##消息的工作流程
上一节我们主要讲了消息相关的SEL和IMP的概念,同时也了解到消息最后都会转换成类似objc_msgSend的消息函数来执行,那么 objc_msgSend 到底是怎么工作的呢?
在Objective-C中,消息直到运行时才会绑定到方法的实现上。编译器会把代码中[receiver message]
转换成 objc_msgSend消息函数,这个函数完成了动态绑定的所有事情。它的运行流程如下:
检查receiver是否为nil,如果为nil,直接cleanup,然后return。这也是我们可以向nil发送消息的原因。
检查message对应的selector是否需要忽略。(ps: Mac开发中开启GC就会忽略retain,release方法。)
在receiver对应的Class中根据Selector去找IMP
在Class中寻找IMP的过程:
- 查找当前class的cache方法列表(cache methodLists)里去找
找到了,跳到对应函数实现; - 没找到,就从class的方法列表(methodLists)里查找;
- 查找当前class的cache方法列表(cache methodLists)里去找
如果在receiver对应的Class中没有找到,就到super class的方法列表里此处输入代码找,直到找到基类(NSObject)为止。
如果在所有的super Class 方法中都没有找到,就会走到动态方法解析的流程中。动态方法解析是说你可以动态地提供一个方法的实现。
我们可以通过分别重载resolveInstanceMethod:和resolveClassMethod:方法分别添加实例方法实现和类方法实现。因为当 Runtime 系统在Cache和方法分发表中(包括超类)找不到要执行的方法时,Runtime会调用resolveInstanceMethod:或resolveClassMethod:来给程序员一次动态添加方法实现的机会。我们需要用class_addMethod函数完成向特定类添加特定方法实现的操作。
例如我们要添加一个名为resolveThisMethodDynamically
的对象方法,那么我们就需要添加如下代码:12345678910111213void dynamicMethodIMP(id self, SEL _cmd) {// implementation ....}@implementation MyClass+ (BOOL)resolveInstanceMethod:(SEL)aSEL{if (aSEL == @selector(resolveThisMethodDynamically)) {class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");return YES;}return [super resolveInstanceMethod:aSEL];}@end
上面的例子为resolveThisMethodDynamically
方法添加了实现内容,也就是dynamicMethodIMP
方法中的代码。其中 “v@:” 表示返回值和参数,这个符号涉及 Type Encoding。
- 如果没有对消息进行动态方法解析处理,那么我们就会走到receiver重定向的流程。
重定向是runtime系统在消息转发机制执行前,给我们的又一次偷梁换柱的机会,即通过重载- (id)forwardingTargetForSelector:(SEL)aSelector
方法替换消息的receiver。1234567- (id)forwardingTargetForSelector:(SEL)aSelector{if(aSelector == @selector(mysteriousMethod:)){return alternateObject;}return [super forwardingTargetForSelector:aSelector];}
毕竟进行消息转发要耗费更多时间,如果可以的画,抓住这次机会将消息重定向给其他对象进行处理是一个不错的选择。重定向绝对不可以返回self,因为那样会造成死循环。
- 消息转发
当动态方法解析不作处理返回NO时,也没有进行对象的重定向,那么消息转发机制就会被触发。在这时forwardInvocation:
方法会被执行,我们可以重写这个方法来定义我们的转发逻辑:12345678- (void)forwardInvocation:(NSInvocation *)anInvocation{if ([someOtherObject respondsToSelector:[anInvocation selector]])[anInvocation invokeWithTarget:someOtherObject];else[super forwardInvocation:anInvocation];}
该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现forwardInvocation:
方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。
最后,需要注意一点:如果想要通过动态方法解析、重定向或者是消息转发把对象A的消息message转发给另一个对象B的话,那么在A对象中必须没有消息message的对应处理,否则会转发失败。