關于isa走位的2道經(jīng)典面試題

第一道經(jīng)典面試題:類方法的歸屬分析

關于類方法和實例方法的歸屬分析,我們首先得知道:實例方法是在類中,而類方法是在元類中。下面我們通過一道面試題來驗證一下。

  • 我們首先創(chuàng)建一個類: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函數(shù)中調(diào)用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這個方法里面的實現(xiàn)
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個方法,結(jié)果是獲取不到實例方法sayHello,但可以獲取到類方法sayHappy。
  • 通過這個方法我們足可以驗證實例方法是在類中,類方法是在元類中。
  • 接下類我們分析一下lgInstanceMethod_classToMetaclass這個方法的實現(xiàn)
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,方法的實現(xiàn)如下:
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
}
  • 通過打印信息,我們發(fā)現(xiàn)為什么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;
}
  • 通過走源碼打斷點,我們發(fā)現(xiàn):
  • imp2imp3 進入到 _objc_msgForward這個方法中,這個方法是消息轉(zhuǎn)發(fā)機制, 沒有返回imp的地址. imp2是元類獲取實例方法sayHello,因為sayHello是實例方法不在元類中,所以獲取不到imp, 就走到了 _objc_msgForward方法中。同理:imp3是類中獲取類方法sayHappy, 由于類方法是在元類中不在類中,所以獲取不到imp,就走到了_objc_msgForward方法中。關于消息轉(zhuǎn)發(fā)機制_objc_msgForward我們后面會講到的。
  • imp1是類中獲取到了實例方法sayHello,會返回imp.
  • imp4是元類中獲取到了類方法sayHappy,也會返回imp.

第二道經(jīng)典面試題的解析:

        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類調(diào)用isKindOfClass的類方法,我們進入isKindOfClass類方法的源碼,打斷點發(fā)現(xiàn),居然不走這個類方法,而是走了objc_opt_isKindOfClass``這個方法,這是由于編譯器優(yōu)化了,實際和走isKindOfClass一樣的結(jié)果。由于是調(diào)用類方法,前面我們講到,類方法是在元類中,所以我們首先到元類找,有沒有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調(diào)用isKindOfClass的類方法,也會走到objc_opt_isKindOfClass這個方法。LGPerson首先找到元類,再到根源類,根類,nil中找和LGPerson不一致,所以返回NO。
  • re5NSObject的實例方法調(diào)用isKindOfClass,也會走到objc_opt_isKindOfClass這個方法, 由于實例方法就在類中,所以直接可以在類中找到和NSObjct一致,返回YES.
  • re7LGPerson的實例方法調(diào)用isKindOfClass,也會走到objc_opt_isKindOfClass這個方法,同理,直接在類中就可以找到LGPerson,所以返回YES.
  • 我們來看一下isMemberOfClass的實例方法和類方法源碼:
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
  • re2re4由于都是調(diào)用isMemberOfClass的類方法,所以都會先找元類self->ISA()中和傳入的類cls相比較,re2中傳入的NSObject的根類和NSObject的元類不相等,所以返回NO。re4中傳入的LGPerson的根類和LGPerson的元類不相等,所以返回NO。
  • re6re8都是調(diào)用了isMemberOfClass的實例方法,所以會走[self class]方法和傳入的類cls相比較。由于傳入的類調(diào)用[self class]和傳入的cls是一樣的,所以都會返回YES。
  • 最后附上一張isa走位的流程圖:
    isa流程圖.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容