环境:xcode 11.5
源码:objc4-781
非常重要的一幅图
首先回归一下objc_msgSend
前面的流程:
- 汇编代码中在
当前class
的cache
中通过进行快速查找,核心函数是CacheLookup
- 没找到的话在
当前class
的方法列表中进行查找,然后在父类
的cache
和方法列表中进行慢速查找,依次往上查询。 - 如果查找过程中遇到
父类==nil
或者父类cache
中的imp=imp_forward
,开始执行一次方法解析并返回结果。
方法解析
方法解析的入口函数为:resolveMethod_locked
,首先我们看两个方法:
resolveInstanceMethod
当前的类为非元类,即普通类的时候,动态解析会执行resolveInstanceMethod
,方法实现如下:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// 查找一下有没有实现resolveInstanceMethod类方法
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 调用类方法resolveInstanceMethod,如果有动态添加方法的话,此时会被添加到类的方法列表中去。
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,需要注意的是lookUpImpOrNil的具体实现中beahvior为LOOKUP_CACHE和LOOKUP_NIL,因此如果没有找到imp的话不会执行动态解析产生递归。如果找到的话,会将imp存入当前类的缓存中。
IMP imp = lookUpImpOrNil(inst, sel, cls);
...
}
static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}
resolveInstanceMethod
主要功能如下:
- 检测当前类及其父类是否实现
+resolveInstanceMethod
类方法。 - 调用
+resolveInstanceMethod
方法 - 重新进行慢速查找,如果有动态添加sel对应的imp,则存入缓存中。
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
//查找一下有没有实现+resolveClassMethod类方法
// 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;
// 调用+resolveClassMethod类方法
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
// 在元类列表中查找类方法sel是否实现,有的话放入元类的方法缓存中
IMP imp = lookUpImpOrNil(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
和resolveInstanceMethod
类似:
- 查看当前类是否实现了
+resolveClassMethod
类方法 - 调用
+resolveClassMethod
方法。 - 在元类中查找
sel
对应的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()) {
// 类对象不是元类时,执行resolveInstanceMethod,解析实例方法
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 元类的话需要解析类方法
resolveClassMethod(inst, sel, cls);
// 在元类中查找对应的方法有没有实现
if (!lookUpImpOrNil(inst, sel, cls)) {
// 因为类方法是元类的实例方法,因此需要重新解析一遍实例方法。
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 上述操作中有可能是已经将新增方法加入到了缓存中,因此这次查找增加了LOOKUP_CACHE,可以在当前类的cache中进行一次基于汇编的快速查找
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
resolveMethod_locked
会对当前的类对象进行判断,非元类会执行resolveInstanceMethod
方法,元类的话会执行resolveClassMethod
以及resolveInstanceMethod
方法。
关于动态解析的总结
-
当我们调用实例方法/类方法时,
runtime
会在类/元类以及对应父类的方法缓存以及方法列表中进行查找,如果查找不到则会执行一次方法的动态解析。 -
方法的动态解析允许开发人员动态添加方法到当前的类/元类中去,来解决没有
sel
对应的imp
的问题 -
实例方法对应的是类的
+resolveInstanceMethod
。 -
因为类方法相当于元类的实例方法,因此,类方法动态解析可以对应类的
+resolveClassMethod
方法和元类的+resolveInstanceMethod
方法。 -
因为类和元类都是
NSObject
的子类,因此我们可以通过在NSObject
中重写resolveClassMethod
和resolveInstanceMethod
来实现方法的动态解析。 -
元类的元类根元类也是
NSObject
的子类,因此我们可以只通过在NSObject
中重写resolveInstanceMethod
方法来实现实例方法和类方法的动态解析。
代码验证
实例方法动态解析
上述代码必然会崩溃。因为ZHYPerson
只有对应的方法声明,并没有实现。修改如下:
正常打印没有问题,因为已经添加了对应的imp
。
类方法动态解析
再来看类方法的情况,在+resolveClassMethod
中给元类添加了方法之后也没有问题。
NSObject分类添加方法动态解析类方法和实例方法
接下来看一点稍微高级一点的写法:
增加NSObject
的分类如下:
main
函数如下:
只是在NSObject
的resolveInstanceMethod
方法中对ZHYPerson
类的实例方法instanceMethod
和类方法classMethod
进行了解析,程序运行正常。
tips
细心的人可能会发现,在上图的场景中,会打印出两个来了--instanceMethod
,这里涉及到了消息转发的相关内容,会在后面进行分析。
消息转发
执行动态解析后,runtime
会重新查找一遍缓存和方法列表,确认sel
是否添加了对应的imp
,如果仍然没有找到imp
,将会执行消息转发流程。如下图所示:
forwardingTargetForSelector
是由__forwarding_prep_0__
触发的。点进来看一下:
__forwarding_prep_0__
属于CoreFoundation![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d9b2d7a7a484e239a184d1fabc2d25e~tplv-k3u1fbpfcp-zoom-1.image)
框架,但是该框架并没有开源,我们可以通过hopper反编译工具
进行查看一些端倪。
反汇编查看伪代码
- 通过
lldb
指令找到CoreFoundation
的位置: - 在
finder
中找到对应的文件 - 拖入
hopper
中搜索对应的方法__forwarding_prep_0__
- 点击跳转到
____forwarding___
方法 如果实现了forwardingTargetForSelector
方法,但是返回为空或者forwardingTargetForSelector
未实现时,会执行methodSignatureForSelector
逻辑如下: - 如果未实现
methodSignatureForSelector
方法,跳转到loc_64dd7
位置: ,最终执行了doesNotRecognizeSelector
方法。 - 如果实现了
methodSignatureForSelector
方法,但是返回值为空,跳转到loc_64e3c
位置 最终执行了doesNotRecognizeSelector
方法。
总结
至此,消息转发的流程图如下所示:
关于resolveInstanceMethod
执行两次的分析
上图是第二次执行resolveInstanceMethod
时的堆栈信息,可以发现是被methodSignatureForSelector
方法中调起的。
在CoreFoundation
的[NSObject methodSignatureForSelector:]
可以看到整个的实现。
我们需要注意的是,一般来说我们会对这几个方法进行重写已处理消息转发的自定义流程,如果最终调用到了NSObject
的实现说明我们并没有处理特定的sel
。
NSObject
的默认实现为返回当前所传sel
的方法签名,因此调用了class_getInstanceMethod
来获取对应的实例方法,实现如下:
正是因为lookUpImpOrForward
中LOOKUP_RESOLVER
参数的存在,才会最终重新触发一次resolveInstanceMethod
。