序言
iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址
iOS底层之Runtime探索(一)
iOS底层之Runtime探索(二)
前面的文章中讲到了objc_msgSend
的方法查找过程,在通过lookUpImpOrForward
方法的慢速查找没有找到imp
后,就到了动态方法决议流程resolveMethod_locked
。
动态方法决议
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 元类判断
if (! cls->isMetaClass()) { // 对象方法流程
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else { // 类方法流程
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 判断当前的
class
是否为元类
; - 不是元类,走实力方法流程
resolveInstanceMethod
; - 是
元类
,走类方法流程resolveClassMethod
,判断resolveClassMethod
是否已解决问题,未解决再走resolveInstanceMethod
流程; - 走完
动态决议
流程,重新找一次imp
;
resolveInstanceMethod
方法解析
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 1.查询元类是否实现了resolveInstanceMethod:方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 2.发送消息resolveInstanceMethod:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
// 3.再次查询sel对应的imp,resolveInstanceMethod可动态修改
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
- 定义
resolveInstanceMethod:
方法;lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(true))
:查询元类是否实现了resolveInstanceMethod:方法,cls->ISA(true)
为类isa
指向的元类
,如果未实现直接返回;- 通过
objc_msgSend
发送resolveInstanceMethod:
消息,将返回结果给resolved
;lookUpImpOrNilTryCache(inst, sel, cls)
:再次查询当前类cls
是否实现了sel
对应的imp
。
lookUpImpOrNilTryCache
方法解析
lookUpImpOrNilTryCache
:是直接调用了_lookUpImpTryCache
,behavior
值为LOOKUP_NIL
。_lookUpImpTryCache
:的核心功能是再次走一次快速
和慢速
方法查询。behavior
值为LOOKUP_NIL
,所以即使再次查不到也不会再走resolveMethod_locked
流程。
resolveClassMethod
方法解析
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
// 1.检查元类是否实现方法resolveClassMethod:
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
// 2.通过cls,获取处理的nonmeta
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
// 3.发送resolveClassMethod: 消息
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
// 4.再次查询imp是否实现
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
- 定义
resolveClassMethod:
方法; - 通过方法
lookUpImpOrNilTryCache
,检查元类
是否实现方法resolveClassMethod:
,如果未实现直接返回; - 通过
cls
,获取处理的nonmeta
; - 通过
objc_msgSend
发送resolveClassMethod:
消息,将返回结果给resolved
; lookUpImpOrNilTryCache(inst, sel, cls)
:再次查询当前元类cls
是否实现了sel
对应的imp
。
动态方法决议流程图
动态决议实例
定义一个类LGTeacher
,声明两个方法,不在m文件
实现。
- 实例方法
sayHelllo
;- 类方法
sayByeBye
;
实现方法resolveInstanceMethod:
但不做处理
实例方法
调用实例方法sayHello
方法运行
LGTeacher
中的resolveInstanceMethod:
方法成功进来,并且进来了两次,然后打开resolveInstanceMethod:
中的注释代码运行。
运行成功,我们成功通过动态向类添加方法lg_instanceMethod
处理了sayHello
方法找不到,引起的崩溃问题。
类方法
调用类方法sayByeBye
方法运行
LGTeacher
中的resolveClassMethod:
方法成功进来,也是进来了两次,然后打开resolveClassMethod:
中的注释代码运行。
运行成功,通过动态向元类
添加方法lg_instanceMethod
处理了sayByeBye
方法找不到,引起的崩溃问题。
总结
我们可以在给公共父类
NSObject
添加分类,重写方法实现resolveInstanceMethod:
,这样就可以成功解决所有imp
找不到引起的崩溃问题了,同时我们也可以通过方法命名规范
知道是哪个模块
的哪个功能
引起的问题,做BUG收集。
缺点:
- 如果要对各个功能进行不同处理,需要大量业务功能逻辑判断,变得很复杂;
- 在
NSObject
的分类中,很多系统的方法处理也会进来这里,会降低运行效率。
消息转发
如果我们未实现动态决议方法resolveInstanceMethod:
,调用未实现的方法系统就会崩溃,这是我们通过bt
指令查看堆栈信息
是通过doesNotRecognizeSelector:
抛出错误的,在这之前还会经过_CF_forwarding_prep_0
和___forwarding___
流程,这都是在CoreFoundation
库里,苹果并没有真正开源CoreFoundation
库。
在查阅苹果文档动态方法解析中,提到了消息转发 Message Forwarding,消息转发正是苹果给你提供的imp
找不到的第二次机会。
Sending a message to an object that does not handle that message is an error. However, before announcing the error,
the runtime system gives the receiving object a second chance to handle the message
.
原理
- 当对象发送的
sel
找不到imp
时,系统会发送forwardInvocation:
来通知该对象;forwardInvocation:
是从NSObject
继承而来的,而在NSObject
中只是调用doesNotRecognizeSelector:
抛出错误;- 我们可以重写
forwardInvocation:
来实现消息的转发。forwardInvocation:
的唯一参数是NSInvocation
;- 在
NSInvocation
中,可以指定消息接收者target
,调用invoke
实现消息转发;- 消息转发完成,
返回值
将会返回给原始消息发送者
;
文档里还介绍了有意思的点,在我们自己的类中要实现一个特定功能
的方法,该功能已经在别的类中实现,我们又不能继承
这个类,因为可能设计在不同的模块中,那我们就可以通过forwardInvocation:
把消息转发特有的类去实现,这就实现类似多继承了。
实例
在关于forwardInvocation:的api
文档中,有个重要提示:
除了
forwardInvocation:
之外,还必要重写methodSignatureForSelector:转发消息的机制使用从methodSignatureForSelector
获得的信息来创建要转发的NSInvocation
对象。重写方法必须为给定的选择器提供适当的方法签名
,方法签名
可以是预先制定的,也可以是向另一个对象请求方法签名
。
在LGTeacher
中加上methodSignatureForSelector
和forwardInvocation:
方法后,再调用sayHello
运行
成功运行,进入methodSignatureForSelector
和forwardInvocation:
方法,没有引起崩溃。
定义LGStudent
实现sayHello
方法,在LGTeacher
中的forwardInvocation:
,将消息转发给LGStudent
同样,在类方法的消息流程转发中,跟实例方法的实现是一致的,不过方法上要注意是+
forwardingTargetForSelector
在文档 forwardInvocation中还提供了forwardingTargetForSelector:方法
该方法的主要作用
无法识别的消息首先应
指向的对象
。如果对象实现(或继承)此方法,并返回非nil(和非self)结果,则返回的
对象
将用作新的接收方对象
,消息分派
将恢复到该新对象
。(显然,如果从该方法返回self,代码将陷入无限循环
。)如果您在
非根类
中实现此方法,如果您的类对于给定的选择器没有要返回
的内容,那么您应该返回调用super
实现的结果。该方法为对象提供了一个机会,在更昂贵的
forwardInvocation:
机械接管之前重定向
发送给它的未知消息。当您只想将消息重定向到另一个对象
,并且可以比常规转发快一个数量级
时,这非常有用。如果转发的目标是捕获NSInvocation
,或在转发过程中操纵参数
或返回值
,则此选项不适用
。
- 大概意思是,如果只需要对未识别的消息,做简单的重定向,那么用此方法会比
forwardInvocation:
流程快很多很多,如果有额外的复杂操作,如捕获NSInvocation
、操纵参数
、返回值
则不适用该方法。 forwardingTargetForSelector:
的调用要在forwardInvocation:
的前面。
在forwardingTargetForSelector:
实现重定向到LGStudent
流程图
消息总结
到这里整个Runtime
中对消息
的处理流程objc_msgSend
的解读都已完成,可能不够全面,中间有些细节难免有遗漏,不过重在整个流程的思维探索。
- 方法调用 -->
objc_msgSend(id receiver, Sel)
;- 通过
isa
获取类的地址cls
;- 快速查找流程
CacheLookup
;- 慢速查找流程
lookUpImpOrForward
;- 动态方法解析
resolveMethod_locked
;- 消息重定向
forwardingTargetForSelector:
;- 消息转发
forwardInvocation:
;
有兴趣的伙伴也可以搞一份源码跑一跑
iOS 全网最新objc4 可调式/编译源码
还有编译好的源码的下载地址