阅读视图
iOS底层之分类的加载
iOS底层之类的加载
iOS底层之Runtime探索(三)
序言
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 可调式/编译源码
还有编译好的源码的下载地址