iOS 消息的传递与转发

205 阅读3分钟

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。

1、方法method和selector(选择子)有什么关系

在 Objective-C 中,selector,Method 和 implementation(IMP) 都是 Runtime 的组成部分。在实际开发中它们常常是可以相互转换来处理消息的发送的。选择子代表方法在 Runtime 期间的标识符。为 SEL 类型,虽然 SEL 是 objc_selector 结构体指针,但实际上它只是一个 C 字符串。在类加载的时候,编译器会生成与方法相对应的选择子,并注册到 Objective-C 的 Runtime 运行系统。
得出结论:选择子其实是方法的名称,不同类中方法名相同参数不同的俩个方法,他们的选择子是相同的。

Method的结构体

/// Method
struct objc_method {
    SEL method_name; 
    char *method_types;
    IMP method_imp;
};
  • 方法名 method_name 类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
  • 方法类型 method_types 是个 char 指针,其实存储着方法的参数类型和返回值类型,即是 Type Encoding 编码。(即类型编码)
  • method_imp 指向方法的实现,本质上是一个函数的指针,就是前面讲到的 Implementation。

消息传递

在OC中,给对象发送消息的语法是:

id returnValue = [someObject messageName:parameter];

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

void objc_msgSend(id self, SEL cmd, ...)

第一个参数代表接收者,第二个参数代表选择子,后续参数就是消息中的那些参数,数量是可变的·,所以这个函数就是参数个数可变的函数。

因此,上述以OC形式展现出来的函数就会转化成如下函数:

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

可以看出,在调用方法时,编译器将它转成了objc_msgSend消息发送了,在Runtime的执行过程如下

  • 1、Runtime先通过对象someobject找到isa指针,判断isa指针是否为nil,为nil直接return。
  • 2、若不为空则通过isa指针找到当前实例的类对象,在类对象下查找缓存是否有messageName方法。
  • 3、若在类对象缓存中找到messageName方法,则直接调用IMP方法(本质上是函数的指针)。
  • 4、若在类对象缓存中没找到messageName方法,则查找当前类对象的方法列表methodlist,若找到方法则将其添加到类对象的缓存中。
  • 5、若在类对象方法列表中没找到messageName方法,则继续到当前类的父类中以相同的方式查找(即类的缓存->类的方法列表)。
  • 6、若在父类中找到messageName方法,则将IMP添加到类对象缓存中。
  • 7、若在父类中没找到messageName方法,则继续查询父类的父类,直到追溯到最上层NSObject
  • 8、若还是没有找到,则启用动态方法解析、备用接收者、消息转发三部曲,给程序最后一个机会
  • 9、若还是没找到,则Runtime会抛出异常doesNotRecognizeSelector

综上,方法的查询流程基本就是查询类对象中的缓存和方法列表->父类中的缓存和方法列表->父类的父类中的缓存和方法列表->...->NSObject中的缓存和方法列表->动态方法解析->备用接收者->消息转发