序言
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
找不到的第2次时机。
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 可调式/编译源码
还有编译好的源码的下载地址