关于isa走位的2道经典面试题

第一道经典面试题:类方法的归属分析

关于类方法和实例方法的归属分析,我们首先得知道:实例方法是在类中,而类方法是在元类中。下面我们通过一道面试题来验证一下。

  • 我们首先创建一个类:LGPerson,继承自NSObject,类里面有一个实例方法 sayHello和一个类方法sayHappy
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end
  • 我们在main函数中调用LGPerson
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgObjc_copyMethodList(pClass);
        lgInstanceMethod_classToMetaclass(pClass);
        lgIMP_classToMetaclass(pClass);
    }
    return 0;
}
  • 我们首先来分析一下lgObjc_copyMethodList这个方法里面的实现
void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[I];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        LGLog(@"Method, name: %@", key);
        // 打印信息:Method, name: sayHello
    }
    free(methods);
    const char *classname = class_getName(pClass);
    Class metaClass = objc_getMetaClass(classname);
    Method method1 = class_getInstanceMethod(metaClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    LGLog(@"%@--%@", NSStringFromSelector(method_getName(method1)), NSStringFromSelector(method_getName(method2)));
   // 打印信息:(null)--sayHappy
}
  • 通过第一个打印信息我们可以看出:LGPerson类中获取到的方法只有sayHello,而我们的类中明明有2个方法,还有一个类方法sayHappy怎么没打印出来呢?
  • 我们接着看第二个打印信息:我们通过获取pClass也就是LGPerson的元类metaClass来获取LGPerson中的2个方法,结果是获取不到实例方法sayHello,但可以获取到类方法sayHappy
  • 通过这个方法我们足可以验证实例方法是在类中,类方法是在元类中
  • 接下类我们分析一下lgInstanceMethod_classToMetaclass这个方法的实现
void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
   //  打印信息:lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148
}
  • 通过打印信息我们可以得出:
    1.method1是类中找到了sayHello方法,其地址为:0x1000031b0.
    2.method2是元类中没有找到sayHello方法,其地址为:0x0.
    3.method3是类中没有找到sayHappy方法,其地址为:0x0.
    4.method4是元类中找到了sayHello方法,其地址为:0x100003148.
    通过这个方法我们足可以验证实例方法是在类中,类方法是在元类中
  • 接下来我们分析一下lgIMP_classToMetaclass,方法的实现如下:
void lgIMP_classToMetaclass(Class pClass){
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    // 打印信息:0x100000e60-0x1002c2240-0x1002c2240-0x100000e30
}
  • 通过打印信息,我们发现为什么imp2imp3的地址一模一样,而他们获取的方法却不同。并且imp1imp4的地址不同,这是什么原因呢?分析这个,我们得从objc源码分析了.
    我们打断点进入到class_getMethodImplementation源码中:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}
  • 通过走源码打断点,我们发现:
  • imp2imp3 进入到 _objc_msgForward这个方法中,这个方法是消息转发机制, 没有返回imp的地址. imp2是元类获取实例方法sayHello,因为sayHello是实例方法不在元类中,所以获取不到imp, 就走到了 _objc_msgForward方法中。同理:imp3是类中获取类方法sayHappy, 由于类方法是在元类中不在类中,所以获取不到imp,就走到了_objc_msgForward方法中。关于消息转发机制_objc_msgForward我们后面会讲到的。
  • imp1是类中获取到了实例方法sayHello,会返回imp.
  • imp4是元类中获取到了类方法sayHappy,也会返回imp.

第二道经典面试题的解析:

        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //

        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
        // 打印信息: 1  0  0  0

        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
        // 打印信息:1  1   1   1  
  • 要解析这道题目,我们就必须要知道isKindOfClassisMemberOfClass的实例方法和类方法的isa走位图了,下面我们通过进入objc的源码和断点走位来分析:
  • re1NSObject类调用isKindOfClass的类方法,我们进入isKindOfClass类方法的源码,打断点发现,居然不走这个类方法,而是走了objc_opt_isKindOfClass``这个方法,这是由于编译器优化了,实际和走isKindOfClass一样的结果。由于是调用类方法,前面我们讲到,类方法是在元类中,所以我们首先到元类找,有没有NSObject,如果没找到就再到元类的父类根元类找,如果还没找到就到根元类的父类根类找,还没有找到就到根类的父类nil找,可以得到和NSObject一致,所以返回YES```
+ (BOOL)isKindOfClass:(Class)cls {
    // 类 vs 元类
    // 根元类 vs NSObject
    // NSObject vs NSObject
    // LGPerson vs 元类 (根元类) (NSObject)
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
  • re3LGPerson调用isKindOfClass的类方法,也会走到objc_opt_isKindOfClass这个方法。LGPerson首先找到元类,再到根源类,根类,nil中找和LGPerson不一致,所以返回NO
  • re5NSObject的实例方法调用isKindOfClass,也会走到objc_opt_isKindOfClass这个方法, 由于实例方法就在类中,所以直接可以在类中找到和NSObjct一致,返回YES.
  • re7LGPerson的实例方法调用isKindOfClass,也会走到objc_opt_isKindOfClass这个方法,同理,直接在类中就可以找到LGPerson,所以返回YES.
  • 我们来看一下isMemberOfClass的实例方法和类方法源码:
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

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