消息发送
在Objective-C里面调用一个方法[object method]
,运行时会将它翻译成objc_msgSend(id self, SEL op, ...)
的形式。
objc_msgSend
objc_msgSend
的实现在objc-msg-arm.s
、objc-msg-arm64.s
等文件中,是通过汇编实现的。这里主要看在arm64
即objc-msg-arm64.s
的实现。由于汇编不熟,里面的实现只能连看带猜。
1 | ENTRY _objc_msgSend |
上面的流程可能是这样的:
从CacheLookup
的注释有两处:
calls imp or objc_msgSend_uncached
Locate the implementation for a selector in a class method cache.
即使看不懂汇编代码,但是从上面的注释我们可以猜测,消息机制会先从缓存中去查找。
__objc_msgSend_uncached
通过方法名我们可以知道,没有缓存的时候应该会执行__objc_msgSend_uncached
。
1 | STATIC_ENTRY __objc_msgSend_uncached |
这里的MethodTableLookup
里涉及到objc-runtime-new.mm
文件中的_class_lookupMethodAndLoadCache3
。该函数会调用lookUpImpOrForward
函数。
lookUpImpOrForward
lookUpImpOrForward
会返回一个imp
,它的函数实现比较长,但是注释写的非常清楚。它的实现主要由以下几步(这里直接从缓存获取开始):
- 通过
cache_getImp
从缓存中获取方法,有则返回,否则进入第2步; - 通过
getMethodNoSuper_nolock
从类的方法列表中获取,有加入缓存中并返回,否则进入第3步; - 通过父类的缓存和父类的方法列表中寻找是否有对应的imp,此时会进入一个
for
循环,沿着类的父类一直往上找,直接找到NSObject为止。如果找到返回,否则进入第4步; - 进入方法决议(method resolve)的过程即调用
_class_resolveMethod
,如果失败,进入第5步; - 在缓存、当前类、父类以及方法决议都没有找到的情况下,Objective-C还为我们提供了最后一次翻身的机会,调用
_objc_msgForward_impcache
进行方法转发,如果找到便加入缓存;如果没有就crash。
上述过程中有几个比较重要的函数:
_class_resolveMethod
1 | void _class_resolveMethod(Class cls, SEL sel, id inst) { |
上述函数会根据当前传入的类的是不是一个元类,在_class_resolveInstanceMethod
和_class_resolveClassMethod
中选择一个进行调用。注释也说明了这两个方法的作用就是判断当前类是否实现了 resolveInstanceMethod:
或者resolveClassMethod:
方法,然后用objc_msgSend
执行上述方法。
_class_resolveClassMethod
_class_resolveClassMethod
和_class_resolveInstanceMethod
实现类似,这里就只看_class_resolveClassMethod
的实现。
1 | static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { |
_objc_msgForward_impcache
1 | STATIC_ENTRY __objc_msgForward_impcache |
_objc_msgForward_impcache
用来进行消息转发,但是其真正的核心是调用_objc_msgForward
。
消息转发
关于_objc_msgForward
在objc
中并没有其相关实现,只能看到_objc_forward_handler
。其实_objc_msgForward
的实现是在CFRuntime.c
中的,但是开源出来的CFRuntime.c
并没有相关实现,但是也不影响我们对真理的追求。
我们做几个实验来验证消息转发。
消息重定向测试
1 | // .h文件 |
运行结果:
1 | 2019-03-12 10:18:54.252949+0800 iOSCodeLearning[18165:5967575] BObject send message |
在forwardingTargetForSelector:
处打个断点,查看一下调用栈:
_CF_forwarding_prep_0
和___forwarding___
这两个方法会先被调用了,之后调用了forwardingTargetForSelector:
。
方法签名测试
1 | // .h文件 |
代码执行结果和消息重定向测试的运行结果一致。_CF_forwarding_prep_0
和___forwarding___
这两个方法又再次被调用了,之后代码会先执行forwardingTargetForSelector:
(消息重定向),消息重定向如果失败后调用methodSignatureForSelector:
和forwardInvocation:
方法签名。所以说___forwarding___
方法才是消息转发的真正实现。
crash测试
1 | // .h文件 |
代码运行结果肯定是crash,结合上面的代码我们知道消息转发会调用___forwarding___
这个内部方法。___forwarding___
方法调用顺序是forwardingTargetForSelector:
->methodSignatureForSelector:
->doesNotRecognizeSelector:
我们用一张图表示整个消息发送的过程:
super关键字
我们先查看一下执行[super init]
的时候,调用了那些方法
objc_msgSendSuper2
的声明在objc-abi.h
中
1 | // objc_msgSendSuper2() takes the current search class, not its superclass. |
objc_super
的定义如下:
1 | struct objc_super { |
从上面的定义我们可以知道receiver
即消息的实际接收者,super_class
为指向当前类的父类。
所以该函数实际的操作是:从objc_super
结构体指向的super_class
开始查找,直到会找到NSObject的方法为止。找到后以receiver
去调用。当然整个查找的过程还是和消息发送的流程一样。
所以我们能理解为什么下面这段代码执行的结果都是AObject
了吧。虽然使用[super class]
,但是真正执行方法的对象还是AObject
。
1 | // 代码 |