一. forward_imp
根据前两篇文章,提出两个问题:
forward_imp
是什么?- 如果方法找不到,如何补救?
1.1 forward_imp
是什么?
在上篇文章 imp慢速查找 中知道了:如果方法未找到,即 superclass
一直找到 nil
,任未找到,则 imp
会被设置为 forward_imp
。那么forward_imp
是什么呢? 在慢速查找流程lookUpImpOrForward
方法的第一行代码,即对forward_imp
进行了赋值:
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
复制代码
__objc_msgForward_impcache
是汇编实现的,在objc_msg_arm64.s
中查找到方法的实现:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward //跳转 __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
...
.macro TailCallFunctionPointer
// $0 = function pointer value
br $0 //跳转 imp
.endmacro
...
复制代码
__objc_msgForward_impcache
中调用了 __objc_msgForward
,__objc_msgForward
中 TailCallFunctionPointer
是个宏,前面分析过就是跳转 imp
。x17
寄存器存放的是imp
,从汇编中可以看出跟x17
有关系的就是 __objc_forward_handler
搜索 __objc_forward_handler
汇编中没有具体的实现,可能在 C/C++
源码中,全局搜索objc_forward_handler
,源码如下:
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
// 方法未找到,方法报错,打印内容
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
复制代码
有没有一种很熟悉的赶脚... 没错就是我们日常开发中遇到的常见错误:函数未实现,运行程序崩溃时报的错误描述信息。
1.2 找不到方法,怎么办?
动态方法决议
:慢速查找流程未找到后,会执行一次动态方法决议。消息转发
:如果动态方法决议仍然没有找到实现,则进行消息转发。消息转发分为:快速消息转发、慢速消息转发
。
接下来,来看看 动态方法决议
二. 动态方法决议
在上篇文章 imp慢速查找 当 superclass
为 nil
时,跳出循环执行 resolveMethod_locked
即方法决议
// 如果查询方法的没有实现,系统会尝试一次方法解析
// behavior = 3,LOOKUP_RESOLVER = 2;
// 3 & 2 = 2,条件成立,进入 if {}
if (slowpath(behavior & LOOKUP_RESOLVER)) {
// behavior = behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 1 behavior ^= LOOKUP_RESOLVER;
// 动态方法决议
return resolveMethod_locked(inst, sel, cls, behavior);
}
复制代码
2.1 resolveMethod_locked
源码分析
slowpath(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();
// 判断cls类是否是元类,如果类说明说明调用的是实例方法
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对应的imp返回imp 实际上就是从缓存中取,因为前面已经缓存过了
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
复制代码
流程分析:
- 判断
cls
的类型; - 如果是类,则执行
实例方法
的动态决议resolveInstanceMethod
方法。 - 如果是元类,则执行
类方法
的动态决议resolveClassMethod
方法。如果元类中没有找到该实例方法或者为空,则在元类的实例方法的动态方法决议resolveInstanceMethod
中查找。为什么呢?因为类方法在元类中,是以对象方法的形式存储,所以需要执行元类的实例对象决议方法。也就是说类是元类的实例对象
。 lookUpImpOrForwardTryCache
快速查找和慢速查找sel
对应的imp
然后返回imp
2.2 resolveInstanceMethod
源码分析
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
// inst 对象 // cls 类
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 只要cls的元类初始化 resolve_sel 方法一定有,因为NSObject默认实现了resolveInstanceMethod
// 目的是将 resolveInstanceMethod 方法缓存到cls的元类中
// 通过lookUpImpOrNilTryCache的参数我们知道 `resolve_sel `是类方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 发送消息调用 resolveInstanceMethod 方法
// 通过 objc_msgSend 发送消息 接收者是cls说明是类方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 判断 resolve_sel 方法有没有实现,注意是`resolve_sel`方法
bool resolved = msg(cls, resolve_sel, sel);
// 为什么还有调用 lookUpImpOrNilTryCache 查询缓存和慢速查找呢
// 虽然 resolveInstanceMethod 方法调用了。但是里面不一定实现了sel 的方法
// 所以还是要去查找sel对应的imp,如果没有实现就会把imp = forward_imp 插入缓存中
// 因为慢速查找流程已经走过了,此时 返回的 imp = forward_imp走down和down_unlock
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
// resolved 和 imp 存在说明动态添加了
if (resolved && PrintResolving) {
if (imp) {
...
}
else {
// Method resolver didn't add anything?
...
}
}
}
复制代码
-
首先创建
resolveInstanceMethod
的SEL resolve_sel
-
根据
lookUpImpOrNilTryCache (cls, resolve_sel, cls->ISA(true))
知道resolveInstanceMethod
是类方法,通过快速和慢速查找流程查找resolve_sel
对应的imp
,缓存resolveInstanceMethod
方法 -
直接通过
msg(cls, resolve_sel, sel)
给类发送消息,从这里也能看到resolveInstanceMethod
是类方法
lookUpImpOrNilTryCache(inst, sel, cls)
快速和慢速查找流程:
- 通过
lookUpImpOrNilTryCache
来确定resolveInstanceMethod
方法中有没有实现sel
对应的imp
- 如果实现了,缓存中没有,进入
lookUpImpOrForward
查找到sel
对应imp
插入缓存,调用imp
查找流程结束 - 如果没有实现,缓存中没有,进入
lookUpImpOrForward
查找,sel
没有查找到对应的imp
,此时imp
=forward_imp
,动态方法决议
只调用一次,此时会走done_unlock
和done
流程,即sel
和forward_imp
插入缓存,进行消息转发
2.3 resolveClassMethod
源码分析
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
// inst 类 //cls元类
// 查询元类有没有实现 NSObject默认实现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);
// 类方法相当于元类中的实例方法,同样去快速和慢速的查找
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
...
}
复制代码
resolveClassMethod
在NSobject
中已经实现,只要元类初始化就可以了,目的是缓存在元类中- 调用
resolveClassMethod
类方法,目的是实现可能resolveClassMethod
方法中动态实现sel
对应的imp
imp = lookUpImpOrNilTryCache(inst, sel, cls)
缓存sel
对应的imp
,不管imp
有没有动态添加,如果没有缓存的就是forward_imp
2.4 lookUpImpOrNilTryCache
lookUpImpOrNilTryCache
方法名字,可以理解就是查找 imp
或者 nil
尽可能的通过查询 cache
的方式,在resolveInstanceMethod
方法和resolveClassMethod
方法都调用 lookUpImpOrNilTryCache
extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
// LOOKUP_NIL = 4 没有传参数behavior = 0, 0 | 4 = 4
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
复制代码
首先最后一个参数默认是 behavior
= 0
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
// cls 是否初始化
if (slowpath(!cls->isInitialized())) {
// 没有初始化就去查找 lookUpImpOrForward 查找时可以初始化
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 在缓存中查找sel对应的imp
IMP imp = cache_getImp(cls, sel);
// imp 有值 进入done流程
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
// 是否有共享缓存
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.`preoptFallbackClass(), sel);
}`
#endif
// 缓存中没有查询到 imp 进入慢速查找流程
// behavior = 4 ,4 & 2 = 0 不会进入动态方法决议,所以不会一直循环
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
// (behavior & LOOKUP_NIL) = 4 & 4 = 1
// LOOKUP_NIL 只是配合_objc_msgForward_impcache 写入缓存
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
复制代码
判断cls
是否初始化一般都会初始化的
缓存中查找
- 在缓存中查找
sel
对应的imp
,如果imp
存在跳转done
流程 - 判断是否有共享缓存给系统底层库用的
- 如果缓存中没有查询到
imp
,进入慢速查找流程 慢速查找流程 - 慢速查找流程中,
behavior
=4
,4
&2
=0
不会进入动态方法决议 - 最重要的如果没有查询到,此时
imp
=forward_imp
,跳转lookUpImpOrForward
中的done_unlock
和done
流程,插入缓存,返回forward_imp
done
流程:
done
流程: (behavior
& LOOKUP_NIL
) 且 imp
= _objc_msgForward_impcache
,如果缓存中的是forward_imp
,就直接返回nil
,否者返回的imp
,LOOKUP_NIL
条件就是来查找是否动态添加了imp
还有就是将imp
插入缓存
lookUpImpOrNilTryCache
的主要作用通过LOOKUP_NIL
来控制插入缓存,不管sel
对应的imp
有没有实现,还有就是如果imp
返回了有值那么一定是在动态方法决议中动态实现了imp
三 实例探究
3.1 resolveInstanceMethod
实例探究
在 YJPerson.h
中声明 say1
方法,不实现; 在 YJPerson.m
类中实现 resolveInstanceMethod
方法:
@implementation YJPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod : %@ - %@", self, NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
@end
复制代码
调用:
int main(int argc, const char * argv[]) {
@autoreleasepool {
YJPerson *person = [[YJPerson alloc] init];
[person say1];
}
return 0;
}
复制代码
在崩溃之前确实调用了 resolveInstanceMethod
方法,但为什么会调用两次resolveInstanceMethod
方法呢?
通过 bt
查看堆栈信息:
-
第一次,和我们分析的是一致的,是在查找
say1
时没有找到,会进入动态方法决议,发送resolveInstanceMethod
消息。 -
第二次,底层系统库
CoreFoundation
调起,在消息转发完成以后再次开启慢速查找流程,进入动态方法决议又调用一次resolveInstanceMethod
,所以总共是两次
3.2 动态添加 sayNice
方法
@implementation YJPerson
- (void)sayNice {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod : %@ - %@", self, NSStringFromSelector(sel));
if (@selector(say1) == sel) {
IMP imp = class_getMethodImplementation(self , @selector(sayNice));
Method meth = class_getInstanceMethod(self, @selector(sayNice));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(self , sel, imp, type);;
}
return [super resolveInstanceMethod:sel];
}
@end
复制代码
resolveInstanceMethod
只调用一次,因为动态添加了sayNice
方法lookUpImpOrForwardTryCache
直接获取imp
,然后调用,查找流程结束- 具体流程:
resolveMethod_locked
-->resolveInstanceMethod
(向类发送决议方法,类中是否有处理:将imp指向新的实现) -->lookUpImpOrNilTryCache(inst, sel, cls)
-->_lookUpImpTryCache
(快速查找cache_getImp
,结果没有;慢速查找lookUpImpOrForward
,有了(因为类中已经将imp指向新的实现),同时加入缓存) -->lookUpImpOrForwardTryCache
-->_lookUpImpTryCache
(快速查找cache_getImp
,这时已经有了,直接return imp)
3.3 resolveClassMethod
实例探究
在 YJPerson.h
中声明 +(void)sayClass
方法,不实现 在 YJPerson.m
类中实现 resolveClassMethod
方法:
@implementation YJPerson
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod : %@ - %@", self, NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
@end
复制代码
调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
[YJPerson sayClass];
}
return 0;
}
复制代码
-
在崩溃之前确实调用了
resolveClassMethod
方法,而且调用了两次,调用两次的逻辑和resolveInstanceMethod
方法调用两次是一样的 -
调用
resolveClassMethod
以后,会去查找lookUpImpOrNilTryCache
有没有具体动态实现sel
对应的imp
,元类的缓存中此时有sel
对应的imp
,这个imp
是forward_imp
。lookUpImpOrNilTryCache
里面有判断直接返回nil
,此时直接到resolveInstanceMethod
查找,因为类方法实际上就是元类中的实例方法 -
如果最后还是没有实现
lookUpImpOrForwardTryCache
获取到forward_imp
进入消息转发流程
3.4 动态添加resayClass
方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if (@selector(sayClass) == sel) {
NSLog(@"resolveClassMethod : %@ - %@", self, NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(resayClass));
Method meth = class_getClassMethod(object_getClass([self class]) , @selector(resayClass));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(object_getClass([self class]) ,sel, imp, type);;
}
return [super resolveClassMethod:sel];
}
+ (void)resayClass {
NSLog(@"--%s---", __func__);
}
复制代码
-
resolveClassMethod
只调用一次,因为动态添加了test
方法 -
resolveClassMethod
和resolveInstanceMethod
的调用流程基本一样,如果resolveClassMethod
没有查询到调用一次resolveInstanceMethod
调用
3.5 整合动态方法决议
resolveClassMethod
方法中如果没有动态添加类方法,会调用 元类
中的resolveInstanceMethod
。那么能不能把 resolveInstanceMethod
写到一个公用类中,使 类方法
和 实例方法
都能调用
- 实例方法查找流程:
对象
-->类
-->直到根类
(NSObject
) -->nil
- 类方法查找流程:
类
-->元类
-->直到根类
(NSObject
) -->nil
到最后都找到NSObject
类中,所以这个公用类就是NSObject
分类
@implementation NSObject (YJ)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod : %@ - %@", self, NSStringFromSelector(sel));
if (@selector(say1) == sel)
{
IMP imp = class_getMethodImplementation(self , @selector(sayNice));
Method meth = class_getInstanceMethod(self , @selector(sayNice));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(self ,sel, imp, type);;
}
else if (@selector(sayClass) == sel)
{
IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(resayClass));
Method meth = class_getClassMethod(object_getClass([self class]) , @selector(resayClass));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(object_getClass([self class]) ,sel, imp, type);;
}
return NO;
}
- (void)sayNice {
NSLog(@"%s", __func__);
}
+ (void)resayClass {
NSLog(@"%s", __func__);
}
@end
复制代码
不管是实例方法还是类方法调用,系统都自动调用了resolveInstanceMethod
方法,和上面探究的吻合。 动态方法决议优点
- 可以统一处理方法崩溃的问题,出现方法崩溃可以上报服务器