• 消息
  • 消息传递
  • Method Swizzling
    • 参考资料

    消息

    在C++或Java里,类与类的行为方法之间的关系非常紧密,一个方法必定属于一个类,且于编译时就已经绑定在一起,所以你不可能调用一个类里没有的方法。而在Objective-C中就比较简单了,类和消息之间是松耦合的,方法调用只是向某个类发送一个消息,该类可以在运行时再确定怎么处理接受到的消息。也就是说,一个类不保证一定会响应接收到的消息,如果收到了一个无法处理的消息,那么程序就是简单报一个错而已。

    Objective-C 是 C 语言的超集,所有的方法在底层都是简单朴素的 C 方法,运行时决定了当传入一个消息时具体哪个方法被调用。

    像对象发送一个消息的代码类似于这个:

    1. id returnValue = [someObject messageName:parameter];

    最终这个代码会变成类似下面这个C方法:

    1. id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);

    消息传递

    当一个对象接受到它不能理解的消息时,消息传递机制会被启用。

    在消息真正“不被处理”之前,有三次可以处理它的机会。

    第一次是动态方法解析

    当一个对象接受到它不能理解的消息时,第一个被调用的方法是一个类方法:

    1. + (BOOL)resolveInstanceMethod:(SEL)selector;

    这个方法以传入对象的 selector 作为参数,返回一个布尔值,这个值指示了有没有一个实例方法被添加到类中,使得类现在可以处理这个 selector。

    如果失败,使用替代接收者

    第二次尝试是询问接受这个消息的类,有没有一个替代接受者可以处理这个未知消息,对应的函数是:

    1. - (id)forwardingTargetForSelector:(SEL)selector;

    未知 selector 被传入,如果有替代者,那么替代者对象会被返回,否则返回nil

    最后,使用消息传递机制

    如果上面这些尝试都失败了,只能使用完成的消息传递机制。首先创建一个NSInvocation对象来包装好这个没有处理的消息的所有细节。

    用于传递消息的方法是:

    1. - (void)forwardInvocation:(NSInvocation *)invocation;

    这个方法的实现应该总是调用其父类的实现。最终NSObject的实现会唤醒doesNotRecognizeSelector来触发一个“未处理的 selector” 异常。

    Method Swizzling

    当消息传入一个函数时,实际调用的方法是在运行时决定的。这就允许我们改变某个消息传入时实际调用的方法。通常这种办法用于改变那些看不到源码的类的功能。

    类的方法列表包含了一系列的 selector 名字和对应的实现之间的映射,用于指导动态消息系统去哪里找某个消息对应的实现。这些对应的实现存储为一种叫做IMP的函数指针,其原型如下:

    1. id (*IMP)(id, SEL, ...)

    要想得到某个 selector 对应的实现,可以使用下面的函数:

    1. Method class_getInstanceMethod(Class aClass, SEL aSelector)

    想添加一个实现,可以使用下面的函数:

    1. BOOL class_addMethod(Class class, SEL originalSelector, Method m1, const char* encoding)

    要想交换两个实现,可以使用:

    1. void method_exchangeImplementations(Method m1, Method m2)

    Swizzling 通常被认为是一种黑科技,它容易导致不可预测的行为和难以预知的结果。尽管它不是十分安全,但是只要能做到下面几点,使用它相对而言还是比较安全的:

    • 总是调用方法本来的实现(除非你真的不需要这么做)
    • 避免冲突
    • 明白你在干什么
    • 小心谨慎

    参考资料

    • Objective-C——消息、Category和Protocol