0%

仓鼠小课堂系列

仓鼠小课堂系列

寄存器

2017年01月13日 讲解人:骑神


简单的计算机内部结构。

那么我们的代码在执行的时候会发生什么事情呢?通常来说,我们的APP存储的位置在存储硬盘

先说一个基础知识,上面的结构图当中越往上执行速度越高

在启动应用之后,CPU会到硬盘中寻址找到应用的存储位置,把app的内存加载到内存当中

然后在程序运行期间,基本上就是CPU不断的在寄存器和内存之后交换数据,当然还有其他虚拟内存的东西,这里就不讲了,因为我也不太懂

寄存器是什么呢?在CPU核心周围分布着一堆的存储元件,这些元件的存储容量很小,一半来说基本都是K为单位的

这些元件被称为寄存器,寄存器会因为自身的功能被分为多种类型,例如寻址寄存器、浮点数寄存器、堆栈等等

作为开发者,我们一般关心的只有通用寄存器,因为你从汇编中看到的代码基本上也是在操作这些寄存器

通用寄存器采用的结构是栈式结构,这种结构有什么好处呢?


如右边的递归代码,在调用的时候会把函数的地址一个个推入寄存器栈中

在我们连续调用一系列的方法的时候,为了能让函数执行完毕之后回到原来的代码位置,系统会把当前调用的代码处的地址推入调用栈中

除此之外,函数调用需要用到的变量会在其他寄存器也进行入栈处理

函数调用完毕之后出栈获取调用处的地址,然后返回继续执行指令

接着就是昨天🐹所说的为什么递归无返回或者递归次数达到一定次数的时候会发生崩溃,原因就是寄存器的尺寸是非常有限的

所以递归次数达到一定数量的时候,寄存器就会容纳不下

delegate和block

2017年01月13日 讲解人:骑神

先来看看delegate跟block的代码

delegate的是NSURLConnectDataDelegate的方法列表,大家可以看到在NSURLConnection的请求过程中,代理会有多次的回调,分别对应图中注释的情况

block图上有两个block,上面是一个日常开发常见的网络请求的处理,下面是NSURLSession创建一个请求任务的代码

NSURLSession除了通过这种方式创建请求任务和传入请求结果处理之外。它自身存在多个类似NSURLConnectionDelegate的回调方法列表。

delegate跟block在设计上的理念就是不同的

因为如果block不是面向结果的设计,那么block的代码应该会把请求过程中的所有状态都囊括起来

上面是从设计理念来说,接下来聊聊两者的运行效率

大家都知道OC是一门消息发送语言,简单来说,就是[a add: b]; 在消息发送语言中被称作
向接收者a发送了一个名为add:的消息,参数是b

基于runtime的OC在执行这段代码的时候会把它转换成用来发送消息的objc_msgSend函数执行代码。这时候就会变成 objc_msgSend(a, @selector(add:), b);

由于昨天的🐹小课堂里已经聊过objc_msgSend过程中会发生什么,这里就不再重复阐述。但是大家要注意这个过程中会依次 遍历消息缓存列表、本类的消息列表、父类的消息列表来直到找到对应的执行代码为止,因此即便苹果做成了汇编的调用,消耗还是有的

那么block的调用消耗呢?

在runtime里面,提供了imp跟block的相互转换。imp是一种用来把代码表示成变量的类型,简单来说跟block的性质是一样的。在objc_msgSend的过程中,目的是为了找到@selector()对应的IMP代码,然后执行

因此block的调用等同于直接调用IMP,省去了查找IMP的过程,从效率上来说胜于代理

是runtime提供了imp跟block的转换方法。实际上block跟imp都等同于指向某段代码的地址,所以效率更高

主要是block->invoke()的调用效率确实太高。至于block内部的结构实现,这个暂且不说

从使用难度上来说,这个包括debug难度跟使用难度,先从debug难度说。因为要我去写一段崩溃的delegate跟block总觉的蛋疼,所以没图

上面也说了,delegate的回调过程中因为涉及到了objc_msgSend的过程,如果在这个过程中发生了异常崩溃,那么在控制台会输出消息发送错误的异常信息,可以比较容易的定位到错误位置

其二,delegate可以通过command+左键的方式跳转到代理方处,所以也能快速的查找到代理双方。从debug的角度来说,难度较低

至于block,通过上面我发的结构图和调用的流程来说,不存在消息发送的过程。因为block是内部结构直接存在函数的指针,直接调用,另一方面是会引用外部对象。所以相比delegate,一旦发生崩溃,难以定位到崩溃处,崩溃原因也基本是野指针异常

因此从debug难度来说,block弱于delegate

那么接下来从使用的角度来说

上面是一个需求,点击商品会弹出商品信息,要用户输入商品的数量,点击确定后商品对应的单元格会高亮
因此代码的逻辑是:
点击单元格->弹窗->输入数量点击确定->单元格高亮

下面放上block跟delegate的处理代码


首先是两个相关的view的头文件


这个是逻辑代码

block的做法会把处理的逻辑写在一处位置,这样子结构清晰。


然后是delegate的处理,就要声明两个协议


这时候弹窗和高亮的逻辑会被分到两个代理事件中处理,在代码量大的时候,连贯性差
而且必要的时候还要清空过渡变量

上面对比的个人总结是:
在事件回调的处理上,delegate比不上block。原因不仅限于block的灵活性更强(当参数传递),功能性(适用于大多数的场景),甚至可以说block是iOS回调手段中最强大的

那么就有一个问题:既然block这么强大,为什么我们还要使用delegate?

这种是代理优于block的场景之一,因为这些代理的事件第一是没有太多的关联性,第二是类型多,事件状态多

另外其实很多人对delegate纠结的一点是:只要提到了协议,很多人的第一反应就是代理

协议可以提供一套共同的行为,比如动物都能喝水,汽车都要加油.
是一种OC实现多继承的方式

好了,最后是协议除了delegate还能做什么?

NS_DESIGNATED _ INITIALIZER的正确使用

2017年02月08日09:54:04

我们随便点开一个系统类的 .h,会发现有些 init 方法后面跟了这个标记:NS_DESIGNATED_INITIALIZER,比如 NSURL :

UIView:

有的 init 方法后面有,有的没有。这个标记是什么意思呢?这种设计有什么好处呢?~

NS_DESIGNATED _ INITIALIZER的正确使用

有这个标记的方法称为指定构造器,与之相对的称为便捷构造器。地铁上就打中文了,大家明白意思就好

可以说,指定构造器是 核心 的init方法,里面应该做这个类初始化时所有 必须 做的事情。

而便捷构造器是搭便车的方法,它不能独立存在,需要调用指定构造器。

举个例子,UIView里面有两个指定构造器,分别是initWithFrame 和 initWithCoder,代表了这个类两种截然不同的初始化方式,它们互相独立且缺一不可

比如仓鼠喜欢为固定尺寸的UIView写一个便捷init方法叫 initWithDefaultFrame,里面调用initWithFrame后面传预设好的参数,这样它的使用者构造它的时候就不用再设尺寸了

我写的这个就是一个典型的便捷构造器

有一条原则就是子类的指定构造器必须调用父类的指定构造器,也就不难理解了

这样的设计有什么好处呢?我能想到的一点是,当你为一个类添加一个必须初始化的属性时,你只需在指定构造器里对它初始化,就可以保证所有的init方法,包括这个类的所有子类,都能正确初始化这个属性

而不是说,我有10个init方法,就得改10个地方

或者担心子类会绕过我的关键初始化

对这个机制还有兴趣进一步了解的同学,可以参考下swift的文档

TheSwiftProgrammingLanguage–语言指南–构造过程