我们在 OC底层原理12-lookUpImpOrForward源码分析(方法查找慢流程) 一文中,分析了方法查找慢流程,会递归找父类的cache
,然后找methods
,直到找到NSObject
的父类nil
,就会给imp
赋值一个forward_imp
,跳出循环,来到resolveMethod_locked
开始方法决议
一、准备工作
1.1、objc4可编译源码,可直接跳到文章最后,下载调试好的源码
1.2、在源码中新增类GomuPerson
如下
GomuPerson.h
- (void)sayNO;
GomuPerson.m
//不实现方法,方便后面断点研究
二、源码探索
2.1 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()) {
//: -- cls不是元类,代表sel是实例方法
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//: -- cls是元类,代表sel是类方法
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
//: -- 如果resolveClassMethod找到了,就不会走这里了
//: -- 如果没找到,这个if必然会走,之前调用lookUpImpOrForward,已经给该sel的方法缓存了imp = forward_imp
//: -- 必然会走到done_nolock,返回一个nil
//: -- 类方法在元类中也是以实例方法的形式存在,所以还需再走一遍实例方法的动态方法决议流程
resolveInstanceMethod(inst, sel, cls);
}
}
//: -- 可能已经有了缓存,所以再给一次机会查询
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
2.2 resolveClassMethod
源码(类方法的动态方法决议)
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//: -- 容错处理,判断该元类的继承链中是否有resolveClassMethod方法
//: -- 如果自定义没实现,则会找到NSObject
if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
//: -- 得到元类/类的对应的类
//: -- 因为我们只能在类里实现resolveClassMethod方法,无法去元类实现,所以这里把消息接受者设置为当前类
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);
}
}
//: -- 发送消息,调用nonmeta中的`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
//: -- 再去查询一次imp,如果上面调用用nonmeta中的`resolveClassMethod `方法里面给元类添加了imp,就会直接找到
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));
}
}
}
2.3 resolveInstanceMethod
源码(实例方法的动态方法决议)
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//: -- 容错处理,判断该元类的继承链中是否有resolveInstanceMethod方法
//: -- 如果自定义没实现,则会找到NSObject
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
//: -- 发送消息,调用cls中的`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
//: -- 再去查一次imp,如果在cls的继承链上自定义实现了`resolveInstanceMethod`方法并在里面添加了imp,就可以找到imp
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 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详解
3.1 resolveInstanceMethod
官方文档
3.2 在GomuPerson.m
中实现resolveInstanceMethod
GomuPerson.m
//- (void)sayNO{ NSLog(@"调用:%s",__func__); }
- (void)sayCode{ NSLog(@"调用:%s",__func__); };
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sayNO)) {
NSLog(@"sayNO来了");
}
return [super resolveInstanceMethod:sel];
}
//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];
//: -- 打印:
sayNO来了
sayNO来了
-[GomuPerson sayNO]: unrecognized selector sent to class 0x100008520
- 添加
resolveInstanceMethod
后,还是会崩溃,但是崩溃之前打印了2次sayNO来了
- 说明
实例方法没实现
会来到resolveInstanceMethod
这个方法
3.3 在GomuPerson
类中,实现resolveInstanceMethod
,并addMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sayNO)) {
NSLog(@"sayNO来了");
//: -- 获取已有方法sayCode
Method method = class_getInstanceMethod(self, @selector(sayCode));
//: -- 获取已有方法sayCode的imp
IMP imp = class_getMethodImplementation(self, @selector(sayCode));
//: -- 构造class_addMethod需要参数types
const char *types = method_getTypeEncoding(method);
//: -- 给sayNO添加一个指向sayCode的方法
return class_addMethod(self, sel, imp, types);
}
return [super resolveInstanceMethod:sel];
}
//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];
//: -- 打印:
sayNO来了
调用:-[GomuPerson sayCode]
- 当我们实现
imp
之后,发现程序不崩溃 - 只打印了一次
sayNO来了
- 调用
sayNO
,调到了它的imp
指向的sayCode
方法
3.4 在GomuPerson
的分类GomuPerson+Study
中,实现resolveInstanceMethod
,并addMethod
@implementation GomuPerson (Study)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sayNO)) {
NSLog(@"分类sayNO来了");
Method method = class_getInstanceMethod(self, @selector(sayCode));
IMP imp = class_getMethodImplementation(self, @selector(sayCode));
const char *types = method_getTypeEncoding(method);
return class_addMethod(self, sel, imp, types);
}
return [super resolveInstanceMethod:sel];
}
@end
//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];
//: -- 打印:
分类sayNO来了
调用:-[GomuPerson sayCode]
- 和
找方法
的顺序一样,如果分类和类
中都现实resolveInstanceMethod
,只会走分类中的方法
- 在分类中调用
resolveInstanceMethod
,实现imp
指向,也可以防止实例方法未实现
发生的崩溃
3.5 在GomuPerson
的父类GomuFather
中,实现resolveInstanceMethod
,并addMethod
@implementation GomuFather
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sayNO)) {
NSLog(@"父类sayNO来了");
Method method = class_getInstanceMethod(self, @selector(sayCode));
IMP imp = class_getMethodImplementation(self, @selector(sayCode));
const char *types = method_getTypeEncoding(method);
//: -- 把方法添加给父类
return class_addMethod(self, sel, imp, types);
//: -- 或者这样写
//: -- 把方法添加给自己
Class cls = GomuPerson.class;
return class_addMethod(cls, sel, imp, types);
}
return [super resolveInstanceMethod:sel];
}
//: -- 或者这样写
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sayNO)) {
NSLog(@"父类sayNO来了");
Class cls = GomuPerson.class;
Method method = class_getInstanceMethod(cls, @selector(sayCode));
IMP imp = class_getMethodImplementation(cls, @selector(sayCode));
const char *types = method_getTypeEncoding(method);
return class_addMethod(cls, sel, imp, types);
}
return [super resolveInstanceMethod:sel];
}
//: -- 调用
GomuPerson *p = [GomuPerson alloc];
[p sayNO];
//: -- 打印:
父类sayNO来了
调用:-[GomuPerson sayCode]
- 在父类中调用
resolveInstanceMethod
,实现imp
指向,也可以防止实例方法未实现
发生的崩溃 - 第一种之所以可以,是因为
父类中会存子类的方法
,所以拿得到sayCode
的imp
总结:在当前
类
或者类的继承链
或者它们的分类
中现实resolveInstanceMethod
方法,可以防止调用当前类没有实现的实例方法
发生崩溃,但是必须指向这个类或者它的父类或者它们的分类
已现实或者已经储存的方法
四、resolveClassMethod详解
4.1 resolveClassMethod
官方文档
4.2 在GomuPerson.m
中实现resolveClassMethod
GomuPerson.m
+ (void)sayShare{ NSLog(@"调用:%s",__func__); };
//+ (void)sayLove{ NSLog(@"调用:%s",__func__); }
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(sayLove)) {
NSLog(@"sayLove来了");
}
return [super resolveClassMethod:sel];
}
//: -- 调用
[GomuPerson sayLove];
//: -- 打印
sayLove来了
sayLove来了
+[GomuPerson sayLove]: unrecognized selector sent to class 0x100008520
- 添加
resolveClassMethod
后,还是会崩溃,但是崩溃之前打印了2次sayLove来了
- 说明
类方法没实现
会来到resolveClassMethod
这个方法
4.3 在GomuPerson.m
中实现resolveClassMethod
,并addMethod
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(sayLove)) {
NSLog(@"sayLove来了");
Class metaClass = objc_getMetaClass("GomuPerson");
Method method = class_getClassMethod(metaClass, @selector(sayShare));
//: -- 或者
Method method = class_getInstanceMethod(metaClass, @selector(sayShare));
IMP imp = class_getMethodImplementation(metaClass, @selector(sayShare));
const char *types = method_getTypeEncoding(method);
return class_addMethod(metaClass, sel, imp, types);
}
return [super resolveClassMethod:sel];
}
//: -- 调用
[GomuPerson sayLove];
//: -- 打印
sayLove来了
调用:+[GomuPerson sayShare]
- 当我们实现
imp
之后,发现程序不崩溃 - 只打印了一次
sayLove来了
- 调用
sayLove
,调到了它的imp
指向的sayShare
方法
4.4 在GomuPerson.m
分类GomuPerson+Study中实现resolveClassMethod
,并addMethod
@implementation GomuPerson (Study)
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(sayLove)) {
NSLog(@"分类sayLove来了");
Class metaClass = objc_getMetaClass("GomuPerson");
Method method = class_getClassMethod(metaClass, @selector(sayShare));
IMP imp = class_getMethodImplementation(metaClass, @selector(sayShare));
const char *types = method_getTypeEncoding(method);
return class_addMethod(metaClass, sel, imp, types);
}
return [super resolveClassMethod:sel];
}
@end
//: -- 调用
[GomuPerson sayLove];
//: -- 打印
分类sayLove来了
调用:+[GomuPerson sayShare]
如果分类和类
中都实现resolveClassMethod
,只会走分类中的方法
- 在分类中调用
resolveClassMethod
,实现imp
指向,也可以防止类方法未实现
发生的崩溃
4.5 在GomuPerson
的父类GomuFather
中,实现resolveClassMethod
,并addMethod
@implementation GomuFather
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(sayLove)) {
NSLog(@"父类sayLove来了");
Class metaClass = objc_getMetaClass("GomuPerson");
Method method = class_getClassMethod(metaClass, @selector(sayShare));
IMP imp = class_getMethodImplementation(metaClass, @selector(sayShare));
const char *types = method_getTypeEncoding(method);
return class_addMethod(metaClass, sel, imp, types);
}
return [super resolveClassMethod:sel];
}
@end
//: -- 调用
[GomuPerson sayLove];
//: -- 打印
父类sayLove来了
调用:+[GomuPerson sayShare]
- 在父类中调用
resolveClassMethod
,实现imp
指向,也可以防止类方法未实现
发生的崩溃
总结:在当前
类
或者类的继承链
或者它们的分类
中现实resolveClassMethod
方法,可以防止调用当前类方法没有实现
发生的崩溃,虽然类方法是以实例方法的形式存在元类
中的,但是获取imp
的时候,不管使用实例方法
还是类方法
都可以获取到
五、在NSObject的分类中实现resolveInstanceMethod和resolveClassMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sayNO)) {
NSLog(@"NSObject分类sayNO来了");
Method method = class_getInstanceMethod(self, @selector(sayCode));
IMP imp = class_getMethodImplementation(self, @selector(sayCode));
const char *types = method_getTypeEncoding(method);
Class cls = GomuPerson.class;
return class_addMethod(cls, sel, imp, types);
}
//: -- 类方法的可以写在这里,因为元类的父类可以找到根元类中,根元类的父类是NSObject,类方法在元类中继承链中找不到,最后会找到NSObject的实例方法
if (sel == @selector(sayLove)) {
NSLog(@"NSObject分类sayLove来了");
Class metaClass = objc_getMetaClass("GomuPerson");
Method method = class_getClassMethod(metaClass, @selector(sayShare));
IMP imp = class_getMethodImplementation(metaClass, @selector(sayShare));
const char *types = method_getTypeEncoding(method);
return class_addMethod(metaClass, sel, imp, types);
}
return NO;
}
//: -- 类方法的动态方法决议可以写在NSObject的实例方法的动态方法决议中
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(sayLove)) {
NSLog(@"NSObject分类sayLove来了");
Class metaClass = objc_getMetaClass("GomuPerson");
Method method = class_getClassMethod(metaClass, @selector(sayShare));
IMP imp = class_getMethodImplementation(metaClass, @selector(sayShare));
const char *types = method_getTypeEncoding(method);
return class_addMethod(metaClass, sel, imp, types);
}
return NO;
}
总结:
类方法的动态方法决议
可以写在NSObject
的实例方法的动态方法决议
中,类方法
存在元类中
,元类的父类
可以找到根元类
中,根元类的父类
是NSObject
,类方法
在元类的继承链
中找不到,最后会找到NSObject的实例方法
- 如果
NSObject
的有类方法的动态方法决议
,递归找元类的父类
的时候,会找到根元类
,如果只有NSObject
的有实例方法的动态方法决议
,则会从根元类
找到NSObject
,会多找一次- 不建议在
NSObject的分类
直接调用动态方法决议
,会污染NSObject
,如果封装在SDK中,则容易被其他人写的NSObject分类
覆盖,这样写侵入性会比较强