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分类覆盖,这样写侵入性会比较强
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342