OC底层原理13-动态方法决议

我们在 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官方文档

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指向,也可以防止实例方法未实现发生的崩溃
  • 第一种之所以可以,是因为父类中会存子类的方法,所以拿得到sayCodeimp

总结:在当前或者类的继承链或者它们的分类中现实resolveInstanceMethod方法,可以防止调用当前类没有实现的实例方法发生崩溃,但是必须指向这个类或者它的父类或者它们的分类已现实或者已经储存的方法

四、resolveClassMethod详解

4.1 resolveClassMethod官方文档

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分类覆盖,这样写侵入性会比较强

你可能感兴趣的:(OC底层原理13-动态方法决议)