由上面一章中,我们了解了什么是RunTime的数据结构,下面了解一下Runtime
的消息发送。
我们知道[object doSomething]
被编译器转化为:
id objc_msgSend ( id self, SEL op, ... );
在Objective-C
中,消息直到运行时才会绑定到方法的实现上。编译器会把代码中[object doSomething]
转换成objc_msgSend
消息函数,这个函数完成了动态绑定的所有事情,它的运行流程如下:
- 检查
selector
是否需要忽略。 - 检查
target
是否为nil
。如果为nil
,直接cleanup
,然后return
。(这就是我们可以向nil
发送消息的原因。) - 然后在
target
的Class
中根据Selector
去找IMP
。
寻找IMP
的过程:
- 先从当前
class
的cache
方法列表(cache methodLists
)里去找。 - 如果找到了,跳到对应函数实现。
- 如果没找到,就从
class
的方法列表(methodLists
)里找。 - 如果还找不到,就到
super class
的方法列表里找,直到找到基类(NSObject
)为止。 - 最后再找不到,就会进入动态方法解析和消息转发的机制。
注意:
- 这里说的分发表其实就是
Class
中的方法列表,它将方法选择器和方法实现地质联系起来。 - 消息的发送其实就是先确定
object
接受者对象,然后根据isa
指针查找其方法然后跳转过去并执行。 - 但是编译期间,是无法确定
object
接受者对象。只有在程序运行期间,object
接受者对象才能得到确定。这种在运行期间才确定object
接受者对象,Objective-C
称为动态绑定。 - 消息发送这种工作机制明显区别另一著名面向对象编程语言——
C++
或者Java
。C++
或者Java
调用对象的函数,函数与对象之间的关系,在编译期间就必须严格确定。比如Car
这个对象里面没有定义函数名为fly
的函数,编译器不会通过,而是会报错。Objective-C
如果向Car
这个对象发送字符串为fly
的selector
,即使car
没有实现fly
方法,编译器依然能够通过,但是运行期间则会因为获取不到实际执行的方法而抛出异常。 - 消息发送的设计使得编译期间
Objective-C
非常包容对象所属的类。在Objective-C
语言中你可以向任何包括空指针nil
在内的对象发你想发的消息。 - 消息发送的机制使得在不重新编译的情况下,在运行期间,干预或者说
hook
原来的target
(方法、变量等)变得更易于实现,更有实际应用价值,这个是需要依赖于消息发送和动态绑定的实现机制——Runtime
。
网友评论