lookUpImpOrForward
函数为慢速查找流程的入口,进入慢速查找流程后,仍未找方法的实现,会进入下一个流程-动态方法解析。
一、动态方法解析的源码分析
我们来看一下 lookUpImpOrForward
函数跳转至动态方法解析的代码:
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
// ^= : 异或运算符,相同为0,不同为 1。
// behavior = 3,LOOKUP_RESOLVER = 2.
// behavior: 0011
// LOOKUP_RESOLVER: 0010
// 结果: 0001
// behavior = 1.
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
当第一次进入判断时,behavior
等于 3,为什么?,我们把源码跑起来。跑起来之前,做一些准备工作。
先声明一个继承自 NSObject
的 SHPerson
对象,声明 helloWorld
方法,但不去实现它,代码如下:
@interface SHPerson : NSObject
- (void)helloWorld;
@end
@implementation SHPerson
@end
接下来在 slowpath(behavior & LOOKUP_RESOLVER)
判断中打一个断点。
behavior = 3,LOOKUP_RESOLVER = 2,3 & 2 = 2。条件成立,所以进入 if 的代码块。
^= : 异或运算符,相同为 0,不同为 1。3 ^= 2 = 1,那么 behavior = 1,传入 resolveMethod_locked
函数。
1、resolveMethod_locked 函数
这是 resolveMethod_locked
函数的内部实现:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 判断是否是元类,如果不是元类,查找实例方法,否则查找类方法。
// 在整个 OC 的底层,没有所谓的实例方法和对象方法,之所以在 OC 层面有是因为 Apple 为了更加体现面向对象。
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 动态解析实例方法。如果没有实现 sel,系统提供接口,给开发者一次机会,避免程序崩溃。
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 动态解析类方法。本质上是查找元类的对象方法。
resolveClassMethod(inst, sel, cls);
// lookUpImpOrNilTryCache 检查是否有缓存,目的是检查是否动态解析类方法
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
// 在动态解析类方法后,开发者在 NSObject 的处理实现的可能不是类方法,而是元类方法。
// 所以在这里又会对实例方法进行解析,这里就是和 isa 流程图中的 superclass 的走位相呼应。
// 根元类的 superclass 指针指向根类。
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 如果前面的处理了,再去查找一次 lookUpImpOrForwardTryCache->_lookUpImpTryCache->lookUpImpOrForward。
// 如果动态解析方法失败
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
resolveMethod_locked
函数的返回值是一个 IMP
。前面一些锁的处理我们先不看。注意看第一个判断,if (! cls->isMetaClass())
是判断函数传进来的 cls
是否是一个元类对象。
我们知道,在类的结构中,存在着类对象和元类对象,它们的结构是一样的,都是 Class
。区别就在于,类对象存储的实例方法,而元类对象存储类方法。所以这个判断的本质上是判断要动态解析实例方法还是类方法。
2、动态解析实例方法
先来看第一个动态解析实例方法 - resolveInstanceMethod
函数的实现:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(true))) {
// Resolver not implemented.
return;
}
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
// 缓存 sel
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));
}
}
}
if (resolved && PrintResolving)
判断后的代码我们忽略,不看。我们来看第一个判断 if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(true)))
,这个判断的意思是判断类中是否实现了 resolveInstanceMethod:
方法。但这个判断永远不会进入,因为系统有默认实现,包括下面要讲的 resolveClassMethod:
方法。
resolveInstanceMethod:
方法的返回值是 BOOL
类型,通过这个返回值告诉系统,我们是否动态方法解析了。不管我们有没有解析,系统都会把 sel
缓存下来,避免重复触发 resolveInstanceMethod:
方法。
3、动态解析类方法
接下来是动态解析类方法,过程和解析实例方法差不多,解析类方法的要实现 resolveClassMethod:
方法,这个是解析类方法的解析器。我们来看一下 resolveClassMethod
函数的实现。
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
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
// 缓存 sel-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
函数的实现,而是 resolveMethod_locked
函数。来看一下 resolveMethod_locked
函数解析类方法的流程:
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 动态解析类方法。本质上是查找元类的对象方法。
resolveClassMethod(inst, sel, cls);
// lookUpImpOrNilTryCache 检查是否有缓存,目的是检查是否动态解析类方法
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
// 在动态解析类方法后,开发者在 NSObject 的处理实现的可能不是类方法,而是元类方法。
// 所以在这里又会对实例方法进行解析,这里就是和 isa 流程图中的 superclass 的走位相呼应。
// 根元类的 superclass 指针指向根类。
resolveInstanceMethod(inst, sel, cls);
}
我们可以看到,在尝试去动态的解析类方法后,会去检查缓存里是否有方法,如果没有方法,会去解析实例方法。为什么呢?
在整个 OC 的底层,没有所谓实例方法和类方法,这些所谓的实例方法和类方法在底层都是函数。那苹果这么做的意义是为了更加体现面向对象。在没有找到对应的类方法实现时,会去实例方法里找,所以才会动态解析实例方法。这里其实就和前面第六篇文章-Objective-C 对象的结构分析中的 isa 流程相呼应。
4、没有实现动态方法解析的处理
如果开发者没有实现动态方法解析的处理,会调用 lookUpImpOrForwardTryCache
函数,它内部的实现就是调用了 _lookUpImpTryCache
函数。
_lookUpImpTryCache
函数的实现如下:
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
先判断类是否初始化,如果没有就会去之前的慢速查找的操作。
如果类初始化了,先进行快速查找,如果缓存有,跳转至 done:
,否则会根据架构的不同,进行处理。
二、动态方法解析 OC 层面处理
在 OC 层面怎么处理呢,系统提供给开发者两个方法 - resolveInstanceMethod:
,resolveClassMethod:
,分别对应动态实例方法解析和动态类方法解析。
1、动态实例方法解析
我们以动态实例方法解析为例,定义一个 dynamicMethodIMP
函数。
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"%s", __func__);
}
定义一个 SHPerson
,在 SHPerson
中声明 helloWorld
方法,但并未实现该方法。代码实现如下
@interface SHPerson : NSObject
- (void)helloWorld;
@end
@implementation SHPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(helloWorld)) {
NSLog(@"%s", __func__);
return class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
实现动态实例方法解析器 - resolveInstanceMethod:
,当 helloWorld
方法不实现的话必然会走到 resolveInstanceMethod:
方法。如果没有实现 helloWorld
方法,我们动态的将 dynamicMethodIMP
函数添加到 SHPerson
,否则就 super
一下。
这里需要注意的是要导入运行时库:#import <objc/runtime.h>
。
调用 helloWorld
方法,并将结果打印,如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
SHPerson *p = [[SHPerson alloc] init];
[p helloWorld];
}
return 0;
}
控制台成功打印出 [SHPerson resolveInstanceMethod:]
和 dynamicMethodIMP
。
2、动态类方法解析
其实动态类方法解析的处理和动态实例方法解析的处理是一样的,只是解析器不一样。动态类方法解析需要实现 resolveClassMethod:
方法,还是用 dynamicMethodIMP
函数处理,SHPerson
的处理如下:
@interface SHPerson : NSObject
+ (void)helloWorld;
@end
@implementation SHPerson
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(helloWorld)) {
NSLog(@"%s", __func__);
return class_addMethod(objc_getMetaClass("SHPerson"), sel, (IMP)dynamicMethodIMP, "v@:");
}
return [super resolveClassMethod:sel];
}
@end
实现 resolveClassMethod:
方法,因为类方法是存储在元类里面,所以在调用 class_addMethod
函数传的第一个参数需要传 SHPerson
的元类。
来看一下调用和打印的结果:
int main(int argc, const char * argv[]) {
@autoreleasepool {
[SHPerson helloWorld];
}
return 0;
}
控制台成功打印出 [SHPerson resolveClassMethod:]
和 dynamicMethodIMP
。
3、验证源码中动态类方法解析流程
前面讲过,如果动态类方法解析没有实现的话会进行动态实例方法解析,下面对此进行验证。
需要注意的是,底层在查找方法的时候,找到根元类了,并且没有找到方法的实现,才会去根类里面去找方法的实现。那么动态方法解析的原理也是一样,只有找到 NSObject
的元类并且没有对类方法解析器做处理,才会走实例方法解析器。
我们调用类方法 helloWorld
,但是实现的解析器是 resolveInstanceMethod:
方法。代码如下:
@interface SHPerson : NSObject
+ (void)helloWorld;
@end
@implementation SHPerson
@end
@implementation NSObject(Category)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(helloWorld)) {
NSLog(@"%s", __func__);
return class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
}
return NO;
}
#pragma clang diagnostic pop
@end
来看一下调用和打印的结果:
int main(int argc, const char * argv[]) {
@autoreleasepool {
[SHPerson helloWorld];
}
return 0;
}
通过打印结果可以知道,虽然调用的是类方法,但是只要在 NSObject
的分类中实现 resolveInstanceMethod:
方法,一样可以做处理,这里就印证前面的源码分析是对的。到这里,整个动态方法解析的流程分析就结束了。