消息转发机制

  消息转发机制是运行时里的一个重要组成部分,它完美的体现了 Objc 动态语言的特性,它可能没有消息发送机制那么有名,但它是我们掌握 Runtime 黑魔法之路上不可或缺的一部分。

在这之前

消息发送

  我之前的关于消息发送的文章里说过,iOS 在对象上调用方法其实是在给某个对象发送一条消息,消息有名称(id)选择子(selector)

  在 objc 中的一个简单的语法,例如

1
2
3
4
5
6
//main.m
int main(int argc, const char * argv[]){
@autoreleasepool{
Person * p = [[Person alloc] init];
}
}

在底层都会变成

底层

  没错,这个上镜率最高的objc_msgSend()函数就是我们消息发送界的带明星了!它就是我们消息发送机制的核心!

消息传递

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

  接收者会根据 isa 指针找到自己所属的类,然后在所属类的”方法列表“(method list)中从上向下遍历。如果能找到与选择子名称相符的方法, IMP 指针会跳转到方法的实现代码。。

  如果找不到与选择子名称相符的方法,接收者会根据所属类的 superClass 指针,沿着类的继承体系继续向上查找(向父类查找),如果能找到与名称相符的方法, IMP 指针就跳转到方法的实现代码,并调用这个方法。

  如果在继承体系中还是找不到与选择子相符的方法,此时就会执行”消息转发(message forwarding)“操作。

消息转发

  我们可以发现,有时候在编译期,向对象或者类对象发送了无法解读的消息,并不会有报错的情况发生,这是因为我们之前也说过, objc是一门动态语言,它可以在类或者元类中添加新的方法,所以编译器在编译期还无法确定是否真的没有这个方法。

  所以当我们写出了一个无法解读的方法的时候,我们可以在消息转发的过程中挽救一下。

消息转发基本有下面几个流程:

  • 动态方法解析:先找接受者属于的类,看能不能动态添加这个方法来处理,如果能,消息转发结束。
  • 重定向:找接受者问问有没有其他对象能处理,如果有,把消息转给那个对象,消息转发结束。
  • 消息签名:找开发者要一个消息签名,如果给nil,消息转发结束。
  • 完整的消息转发:以上流程都不管用的时候,只能把该消息相关的所有细节封装到一个 NSInvocation 对象。再问接受者一次,如果还是不能处理,那消息转发也无能为力了

动态方法解析

对象在收到无法解读的消息后,首先调用其所属类的这个类方法

1
2
3
4
+ (BOOL)resolveInstanceMethod:(SEL)selector
//selector : 消息转发里的未知选择子
//返回YES结束消息转发
//返回NO找后备接受者

如果未知消息是一个了类方法而非实例方法,则会调用resolveClassMethod方法

重定向

动态解析失败,则调用这个方法

1
2
- (id)forwardingTargetForSelector:(SEL)selector
//返回一个能响应该选择子的 “备胎”对象

在这个方法我们可以实现偷天换日,把这个消息发到其他类去实现。

消息签名

“备胎”搞不定,这个方法就要被包装成一个NSInvocation对象,在这里要先返回一个消息签名

1
2
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
//NSMethodSignature : 该sleector对应的方法签名

完整的消息转发

给最后一次机会处理这个方法,如果不行就没了

1
2
- (void)forwardInvocation:(NSInvocation *)invocation
//invocation : 封装了与那条尚未处理消息相关的所有细节对象

这里可以在消息触发前,先改变消息内容,比如追加一个参数或者将一个消息翻译成另一个消息等,forwardInvocation:方法还可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。

总的来说,消息转发可以简单的用以下的流程图展现:

消息转发

总结

  当我们掌握了 Runtime 的消息发送和消息转发之后,我们就可以运用黑魔法做很多事情啦!至于黑魔法的实际运用,我可能会在以后的博文中再写。