runtime的消息发送和转发机制
objc_msgSend
在OC中,所有的消息调用最后都会通过 objc_msgSend 方法进行访问。通过 objc_msgSend 进行消息调用,为了加快执行速度,这个方法在runtime源码中是用汇编实现的。
调用方法的时候底层会objc_msgSend查找方法的imp
发送消息的是一个对象。如果要找类对象需要通过对象的isa指针查找到类对象。
isa是一个isa_t公用体,里面有很多字段。
方法有方法缓存
如果缓存没有 则进行方法列表的遍历
方法缓存存在cache_t中,cache_t存在类对象objc_class结构体中。
在objc_class结构体中根据偏移计算相应结构体指针。
cache_t是一个结构体 里面有一个存储方法缓存的bucket_t数组,bucket_t里面有_imp,_sel。
根据sel和imp在方法缓存中如果没有找到,则来到__objc_msgSend_uncached方法中进行没有方法缓存的查找。
总结
objc_msgSend在查找的时候先进行一个缓存的命中,通过地址的与运算,拿到当前的缓存,和当前要查找的sel进行比对,如果比对上就命中了,直接使用。没有比对上则进行__objc_msgSend_uncached。
__objc_msgSend_uncached
没有查找到缓存的时候,需要遍历方法列表。lookUpImpOrForward。
lookUpImpOrForward
调用 lookUpImpOrForward 方法,返回值是个 IMP 指针,如果查找到了调用函数的 IMP ,则进行方法的访问
- 当前类对象的方法列表中遍历方法列表。
- 沿着当前继承链当中的superClass,指针的指向,来进行方法遍历查找。
- 一直遍历到Root Class(NSObject)
消息转发机制 动态特性
如果没有查到对于方法的 IMP 指针,则进行消息转发机制
_objc_msgForward消息转发做的几件事
-
第一层转发:(动态方法解析)
调用
resolveInstanceMethod:
或resolveClassMethod:
,允许用户在此时为该Class动态添加实现。如果实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。本质上是在方法列表中建立SEL和新的IMP关系。新的IMP是自己做的。如果仍没实现,继续下面的动作。 -
第二层转发:(标准消息转发,obj_msgForward 方法的转发)找一个备用的接收者来处理消息。
如果第一层转发返回 NO ,则会进行第二层转发,调用
forwardingTargetForSelector:
,可以把调用转发到另一个对象,这是类级别的转发,调用另一个类的相同的方法。注意:这里不要返回self,否则会形成死循环。
-
第三层转发:(标准消息转发,obj_msgForward 方法的转发)爱咋咋地,消息拦截了,自己处理。
如果第二层转发返回 nil ,则会进入这一层处理
这层会调用
methodSignatureForSelector:
尝试获得一个方法签名。如果获取不到则直接调用doesNotRecognizeSelector:
抛出异常。如果能获取到,则返回非nil,创建一个NSInvocation并传给forwardInvocation:
。调用
forwardInvocation:
方法,将上面获取到的方法签名包装成Invocation传入,如何处理就在这里面了,并返回nil。这次是完整的消息转发,因为你可以返回方法签名、动态指定调用方法的Target
-
如果转发都失败,调用
doesNotRecognizeSelector:
,crash抛出异常。
_objc_msgForward在进行消息转发的过程中会涉及以下几个方法:
- resolveInstanceMethod:(resolveClassMethod:)
- forwardingTargetForSelector:
- methodSignatureForSelector:
- forwardInvocation:
- doesNotRecognizeSelector:
一旦调用_objc_msgForward将会跳过查找IMP的过程,直接触发消息转发。
网友评论