动态方法解析
之前在分析objc_msgSend
的时候,当慢速查找和快速查找都未找到SEL
时,会根据当前设置的条件(behavior & LOOKUP_RESOLVER)
选择是否进行动态方法解析
,接下来我们就来探索一下动态方法解析的源码
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
// 重新在当前类中查找SEL方法
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
从以上代码看到,主要分为两种可能:1、当前类为非元类,则调用resolveInstanceMethod
方法进行动态解析;2、当前类为元类,则调用resolveClassMethod
方法进行动态解析。
resolveInstanceMethod
resolveInstanceMethod源码
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//在当前类的缓存列表中查找 resolveInstanceMethod: 方法
// 查找 resolveInstanceMethod: 方法仍然采用快速查找和慢速查找的流程
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//执行 [cls resolveInstanceMethod:sel]方法
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
//重新按快速查找,慢速查找的方法,查找SEL
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
resolveInstanceMethod 说明
- 当前inst:
HQPerson对象
; - 当前类cls:
HQPerson类
; - 当前类的isa指向:
HQPerson元类
; - 由于
resolveInstanceMethod
方法是类方法
,因此主要是去当前类的isa所指向的类
中查找是否有该方法实现。 - 查找
resolveInstanceMethod
方法的顺序:当前元类的缓存列表->当前元类的方法列表->根元类的缓存列表->根元类的方法列表->根类的缓存列表->根类的方法列表
。 -
resolveInstanceMethod
是当经过快速慢速查找过程后仍然找不到SEL
对应的IMP
时,苹果提供给我们一次挽救的机会.可以在该方法进对找不到的SEL
进行动态的方法添加.若在该函数中进行了挽救,则在下次进行SEL
方法查找时,就能返回IMP了.否则就会跳转至_objc_msgForward_impcache
. - 重点:
对象 ---isa---> 类(对象的实例方法) ---isa---> 元类(对象的类方法/类的实例方法)
resolveClassMethod
resolveClassMethod源码
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//查找类中是否有resolveClassMethod:实现
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
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);
}
}
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
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
说明
- 当前inst:
HQPerson类
; - 当前类cls:
HQPerson元类
; - 当前类的isa指向:
根元类
; -
resolveClassMethod
方法是类方法
,由于当前类
已经是元类
,因此在当前类
中查找是否有该方法实现。 - 查找
resolveClassMethod
方法的顺序:当前类的缓存列表->当前类的方法列表->根元类的缓存列表->根元类的方法列表->根类的缓存列表->根类的方法列表
。 -
resolveClassMethod
是当经过快速慢速查找过程后仍然找不到SEL
对应的IMP
时,苹果提供给我们一次挽救的机会.可以在该方法进对找不到的SEL
进行动态的方法添加.若在该函数中进行了挽救,则在下次进行SEL
方法查找时,就能返回IMP了. - 若经过前面的一次挽救仍然未找到
SEL
对应的IMP
,则根据前面对象方法查找的思路,可以通过resolveInstanceMethod
函数再进行一次挽救.此时resolveInstanceMethod
方法即是类的类方法,因此需要从根元类查找. - 重点:
类 ---isa---> 元类(类的实例方法) ---isa---> 根元类(类的类方法/元类的实例方法)
总结
- 【步骤1】定义一个
SEL=resolveClassMethod
的方法,并当前类
中查找resolveClassMethod
方法是否有实现,若未实现,则动态解析的方法直接返回,进入【步骤4】】。 - 【步骤2】找到了
动态解析方法(resolveClassMethod)
的IMP
,执行该方法。 - 【步骤3】重新在
当前类
中查找SEL=sel
方法。- 此时若
当前类
仍找到SEL
,则返回当前SEL
的IMP
。 - 此时若
当前类
仍未找到SEL
,则会往当前类
的cache
中将sel
方法的IMP
置为_objc_msgForward_impcache的IMP
,并将结果返回nil
。注意:_objc_msgForward_impcache
即为最终的未找到函数实现的报错打印。
- 此时若
- 【步骤4】判断一下
当前类
中是否有SEL
对应的IMP
,且IMP
不为_objc_msgForward_impcache
。- 若
当前类
中存在SEL
对应的IMP
且不为_objc_msgForward_impcache
,则进入【步骤5】。 - 若
当前类
中存在SEL
对应的IMP
为_objc_msgForward_impcache
,则进入resolveInstanceMethod
流程。- 【步骤4.1】定义一个
SEL=resolveInstanceMethod
的方法,并当前类的isa所指向的类(根元类)
中查找resolveInstanceMethod
方法是否有实现,若未实现,则动态解析的方法直接返回,进入【步骤5】】。 - 【步骤4.2】找到了
动态解析方法(resolveInstanceMethod)
的IMP
,执行该方法。 - 【步骤4.3】重新在
当前类
中查找SEL=sel
方法。- 此时若
当前类
仍找到SEL
,则返回当前SEL
的IMP
。 - 此时若
当前类
仍未找到SEL
,则会往当前类
的cache
中将sel
方法的IMP
置为_objc_msgForward_impcache的IMP
,并将结果返回nil
。注意:_objc_msgForward_impcache
即为最终的未找到函数实现的报错打印。
- 此时若
- 【步骤4.1】定义一个
- 若
- 【步骤5】此时在
resolveMethod_locked
函数中的最后一步,在当前类
中查找SEL
。由于经过【步骤3】和【步骤4】,所以此时一定会查找至SEL
对应的IMP
。存在两种可能:-
SEL
方法的IMP
。 -
_objc_msgForward_impcache
方法的IMP
。
-
resolveMethod_locked 总结
对象 ----isa---> 类(对象的实例方法) ---isa---> 元类(对象的类方法/类的实例方法) ---isa---> 根元类(类的类方法/元类的实例方法)
- 对象方法:
- 提供一次挽救的机会,即在
resolveInstanceMethod
方法中进行挽救. - 根据
对象的类方法存在元类
中这个原理,resolveInstanceMethod
方法的查找顺序:元类->根元类->根类->nil
.
- 提供一次挽救的机会,即在
- 类方法:
- 提供二次挽救的机会,第一次在
resolveClassMethod
方法中进行挽救. -
resolveClassMethod
方法的查找顺序:元类->根元类->根类->nil
. - 第二次在
resolveInstanceMethod
方法中进行挽救. - 根据
类的类方法存在根元类
中这个原理,resolveInstanceMethod
方法的查找顺序:根元类->根类->nil
.
- 提供二次挽救的机会,第一次在
- 对象方法和类方法的共同点,即,都会查找根类中的
resolveInstanceMethod
方法.因此,可以根类中resolveInstanceMethod
函数对类方法/对象方法统一处理.
+(BOOL)resolveInstanceMethod:(SEL)sel {
// NSLog(@"动态添加%@方法", NSStringFromSelector(sel));
if (sel == NSSelectorFromString(@"say666")) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
//获取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, NSSelectorFromString(@"instanceMethod"));
//获取sayMaster的实例方法
Method sayMethod = class_getInstanceMethod(self, NSSelectorFromString(@"instanceMethod"));
//获取sayMaster的丰富签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向sayMaster
class_addMethod(self, sel, imp, type);
return YES;
}
else if (sel == NSSelectorFromString(@"sayNB")) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
//获取sayMaster方法的imp
IMP imp = class_getMethodImplementation(objc_getMetaClass("HQPerson"), NSSelectorFromString(@"classMethod"));
//获取sayMaster的实例方法
Method sayMethod = class_getInstanceMethod(objc_getMetaClass("HQPerson"), NSSelectorFromString(@"classMethod"));
//获取sayMaster的丰富签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向sayMaster
class_addMethod(objc_getMetaClass("HQPerson"), sel, imp, type);
return YES;
}
return NO;
}