Ziank的技术博客

Object-C runtime之消息(2)

##消息的工作流程
上一节我们主要讲了消息相关的SEL和IMP的概念,同时也了解到消息最后都会转换成类似objc_msgSend的消息函数来执行,那么 objc_msgSend 到底是怎么工作的呢?

在Objective-C中,消息直到运行时才会绑定到方法的实现上。编译器会把代码中[receiver message]转换成 objc_msgSend消息函数,这个函数完成了动态绑定的所有事情。它的运行流程如下:
消息处理流程

  1. 检查receiver是否为nil,如果为nil,直接cleanup,然后return。这也是我们可以向nil发送消息的原因。

  2. 检查message对应的selector是否需要忽略。(ps: Mac开发中开启GC就会忽略retain,release方法。)

  3. 在receiver对应的Class中根据Selector去找IMP

    在Class中寻找IMP的过程:

    1. 查找当前class的cache方法列表(cache methodLists)里去找
      找到了,跳到对应函数实现;
    2. 没找到,就从class的方法列表(methodLists)里查找;
  4. 如果在receiver对应的Class中没有找到,就到super class的方法列表里此处输入代码找,直到找到基类(NSObject)为止。

  5. 如果在所有的super Class 方法中都没有找到,就会走到动态方法解析的流程中。动态方法解析是说你可以动态地提供一个方法的实现。
    我们可以通过分别重载resolveInstanceMethod:和resolveClassMethod:方法分别添加实例方法实现和类方法实现。因为当 Runtime 系统在Cache和方法分发表中(包括超类)找不到要执行的方法时,Runtime会调用resolveInstanceMethod:或resolveClassMethod:来给程序员一次动态添加方法实现的机会。我们需要用class_addMethod函数完成向特定类添加特定方法实现的操作。
    例如我们要添加一个名为resolveThisMethodDynamically的对象方法,那么我们就需要添加如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void 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

  1. 如果没有对消息进行动态方法解析处理,那么我们就会走到receiver重定向的流程。
    重定向是runtime系统在消息转发机制执行前,给我们的又一次偷梁换柱的机会,即通过重载- (id)forwardingTargetForSelector:(SEL)aSelector方法替换消息的receiver。
    1
    2
    3
    4
    5
    6
    7
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
    if(aSelector == @selector(mysteriousMethod:)){
    return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
    }

毕竟进行消息转发要耗费更多时间,如果可以的画,抓住这次机会将消息重定向给其他对象进行处理是一个不错的选择。重定向绝对不可以返回self,因为那样会造成死循环。

  1. 消息转发
    当动态方法解析不作处理返回NO时,也没有进行对象的重定向,那么消息转发机制就会被触发。在这时forwardInvocation:方法会被执行,我们可以重写这个方法来定义我们的转发逻辑:
    1
    2
    3
    4
    5
    6
    7
    8
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
    if ([someOtherObject respondsToSelector:
    [anInvocation selector]])
    [anInvocation invokeWithTarget:someOtherObject];
    else
    [super forwardInvocation:anInvocation];
    }

该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。

最后,需要注意一点:如果想要通过动态方法解析、重定向或者是消息转发把对象A的消息message转发给另一个对象B的话,那么在A对象中必须没有消息message的对应处理,否则会转发失败。