iOS开发笔记(三):消息传递与转发机制

2018-01-13 10:55:44来源:https://juejin.im/post/5a55c8956fb9a01c9405bd82作者:稀土掘金人点击

分享


在iOS开发中经常会遇到unrecognized selector sent to instance 0x100111df0
的问题,这是为什么呢,从字面上理解来说是无法识别的selector子发送给对象,其实调用一个不存在的方法就会遇到这个问题。


严格来说iOS中不存在方法调用的说法,应该说是消息的传递。消息传递和函数调用的区别就是,你可以在任意的时候对一个对象发送任何消息,而不需要在编译的时候声明。但是函数调用就不行。


先理解C语言的函数调用方式。C语言使用静态绑定,也就是说,在编译期就能决定程序运行时所应该调用的函数,所以在C语言中,如果某个函数没有实现,编译时是不能通过的。


在Objective-C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息后,究竟该调用哪个方法则完全于运行期决定,甚至可以在程序运行时改变,这些特性使得Objecttive-C成为一门真正的动态语言。


2.消息传递机制
2.1 objc_msgSend()

消息有名称或选择子,可以接受参数,而且可能还有返回值。 给对象发送消息可以这样写:


id returnValue = [someObject messageName:parameter];


someObject:接收者 messageName:选择子(选择器) 选择子与参数合起来称为“消息” 编译器看到此消息后,将其转换成一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数,叫做objc_msgSend,其“原型”如下:void objc_msgSend(id self, SEL cmd, ...)


这是个“参数个数可变的函数,能接受两个或两个以上的参数。第一个参数代表接收者,第二个参数代表选择子(SEL是选择子的类型),后续参数就是消息中的那些参数,其顺序不变。选择子指的就是方法的名字。“选择子”与“方法”这两个词经常交替使用。编译器会把刚才那个例子中的消息转换为如下函数:


id returnValue =objc_msgSend(someObject, @selector(messageName:), parameter);
2.2 消息传递流程

objc_msgSend()函数会一句接受者(调用方法的对象)的类型和选择子(方法名)来调用适当的方法。


接收者会根据isa指针找到接收者自己所属的类,然后在所属类的“方法列表(method list)”中从上向下遍历。如果能找到与选择子名称相符的方法,就根据IMP指针跳转到方法的实现代码,调用这个方法的实现。
如果找不到与选择子名称相符的方法,接收者会根据所属类的superClass指针,沿着类的继承体系继续向上查找(向父类查找),如果能找到与名称相符的方法,就根据IMP指针跳转到方法的实现代码,调用这个方法的实现。
如果在继承体系中还是找不到与选择子相符的方法,此时就会执行“消息转发(message forwarding)”操作。
SEL:类成员方法的指针,但和C的函数指针还不一样,函数指针直接保存了方法的地址,但是SEL只是方法编号。
isa指针:每个对象都有一个标识对象类的isa实例变量。运行时使用此指针来确定对象需要时的实际类。
IMP:函数指针,保存了方法地址。
2.3 快速映射表

我们发现调用一个方法并不像我们想的那么简单,更不像我们写的那么简单,一个方法的执行其实底层需要很多步骤。正因如此,objc_msgSend()会将调用过且匹配到的方法缓存在“快速映射表(fast map)”中,快速映射表就是方法的缓存表。每个类都有这样一个缓存。所以,即便子类实例从父类的方法列表中取过了某个对象方法,那么子类的方法缓存表中也会缓存父类的这个方法,下次调用这个方法,会优先去当前类(对象所属的类)的方法缓存表中查找这个方法,这样的好处是显而易见的,减少了漫长的方法查找过程,使得方法的调用更快。同样,如果父类实例对象调用了同样的方法,也会在父类的方法缓存表中缓存这个方法。


同理,如果用一个子类对象调用某个类方法,也会在子类的metaclass里缓存一份。而当用一个父类对象去调用那个类方法的时候,也会在父类的metaclass里缓存一份。


3.消息转发机制
3.1 动态方法解析

动态方法解析的意思就是,征询消息接受者所属的类,看其是否能动态添加方法,以处理当前“这个未知的选择子(unknown selector)”。实例对象在接受到无法解读的消息后,首先会调用其所属类的下列类方法:


+ (BOOL)resolveInstanceMethod:(SEL)selector

类对象在接受到无法解读的消息后,那么运行期系统就会调用另外的一个方法,如下:


+ (BOOL)resolveClassMethod:(SEL)selector

如果运行期系统已经执行完了动态方法解析,那么消息接受者自己就无法再以动态新增方法的形式来响应包含该未知选择子的消息了,此时就进入了第二阶段——完整的消息转发。运行期系统会请求消息接受者以其他手段来处理与消息相关的方法调用。


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台