iOS底層原理 06 : isa走位&類繼承的經典面試題

1. 下面代碼打印的結果是什么,并做分析。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];  
        BOOL re2 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];     
        BOOL re3 = [(id)[NSObject class] isMemberOfClass:[NSObject 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);
    }
    return 0;
}

打印的結果:

re1 :1
re2 :0
re3 :0
re4 :0

為什么是這個結果呢?
** [[NSObject class] isKindOfClass:[NSObject class]],首先前面是在調用isKindOfClass方法,所以找的是+ (BOOL)isKindOfClass:(Class)cls類方法。**
我們查看源碼

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

對于re1

1.Class tcls = self->ISA(), NSObject類isa指向根元類根元類 != NSObject類,循環繼續
2.接下來tcls = tcls->superclass根元類的父類NSObject類, 此時 tcls == cls ,return YES;

對于re2 **

1.Class tcls = self->ISA(), LGPerson信息類isa指向LGHPerson的元類LGPerson的元類!= LGPerson類,循環繼續。
2. tcls = tcls->superclassLGPerson的元類的父類根元類根元類 != LGPerson類,循環繼續。
3. tcls = tcls->superclass根元類 的父類NSObject類NSObject類 != LGPerson類,循環繼續。
4. tcls = tcls->superclassNSObject的父類nil,循環結束
5. return NO;

同理[(id)[NSObject class] isMemberOfClass:[NSObject class]],我們需要找的是+ (BOOL)isMemberOfClass:(Class)cls類方法。

通過源碼得出: +isMemberOfClass是判斷當前類的isa所指向的類傳入的cls類是否一致

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

對于re3

NSObject類的isa指向根元類根元類 != NSObject類,return NO;

對于re4

LGPerson類的isa指向LGPerson的元類LGPerson的元類 != LGPerson類, return NO;

2. 下面代碼打印的結果是什么,并做分析。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];        
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; 
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject 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);
    }
    return 0;
}

打印的結果:

re5 :1
re6 :1
re7 :1
re8 :1

首先[(id)[NSObject alloc] isKindOfClass:[NSObject class]],我們應該找的是實例方法-(BOOL)isKindOfClass:(Class)cls
查看源碼:
1. 首先是tcls = [self class]cls比較,
2. 接下來tcls->superclass(即父類)cls比較,
3. tcls->superclass->superclass(即父類的父類) 與cls比較
.....for循環
4. 一直到NSObject類cls比較,如果還是不相等,NSObject類->superclassnil,for循環結束,返回NO

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

對于re5

Class tcls = [self class] ,tcls=NSObject類,NSObject類=NSObject類, return YES;

對于re6

Class tcls = [self class] ,tcls=LGPerson類,LGPerson類==LGPerson類,return YES;

對于[(id)[NSObject alloc] isMemberOfClass:[NSObject class]],我們該找的是實例方法-(BOOL)isMemberOfClass:(Class)cls*
查看源碼:

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

對于re7

Class tcls = [self class]tcls=NSObject類NSObject類==NSObject類, return YES;

對于re8

Class tcls = [self class] ,tcls=LGPerson類LGPerson類==LGPerson類,return YES;

2.以下代碼的輸出結果是什么?并分析原因。
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end

@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        const char *className = class_getName(pClass);
        Class metaPClass = objc_getMetaClass(className);
        
        Method method1 = class_getClassMethod(pClass, @selector(sayHello));
        Method method2 = class_getClassMethod(metaPClass, @selector(sayHello));
        Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
        Method method4 = class_getClassMethod(metaPClass, @selector(sayHappy));
        LGLog(@"method1=%p",method1);
        LGLog(@"method2=%p",method2);
        LGLog(@"method3=%p",method3);
        LGLog(@"method4=%p",method4);
        
        Method method5 = class_getInstanceMethod(pClass, @selector(sayHello));
        Method method6 = class_getInstanceMethod(metaPClass, @selector(sayHello));
        Method method7 = class_getInstanceMethod(pClass, @selector(sayHappy));
        Method method8 = class_getInstanceMethod(metaPClass, @selector(sayHappy));
        LGLog(@"method5=%p",method5);
        LGLog(@"method6=%p",method6);
        LGLog(@"method7=%p",method7);
        LGLog(@"method8=%p",method8);
    }
    return 0;
}

打印結果:

method1=0x0
method2=0x0
method3=0x100003070
method4=0x100003070
method5=0x1000030d8
method6=0x0
method7=0x0
method8=0x100003070

首先我們知道, 實例方法存儲在類的實例方法列表中中,而類方法存儲在元類的實例方法列表中。

查看class_getClassMethod的源代碼.
從源碼得出:
1. 首先它會去找cls的元組實例方法列表,結合getMeta()方法,如果本身是元組類型,直接去找自身的實例方法列表,
2. 然后cls->getMeta()->superclass,去cls的元組的父類里面找,
..... for循環..... ,
3. 一直找到根元類,然后是NSObject類,最后是nil,結束循環。

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}
 
// 如果是元組類型,直接返回,否則返回當前class的isa指向的類
Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }
    return m;
}

class_getClassMethod源碼大致流程圖:

class_getClassMethod源碼大致流程圖.png

sayHello實例方法,所以存在于LGPerson類的實例方法列表
1. method1,所以在LGPerson元類的methods中獲取不到,返回0x00
2. method2, sayHello是實例方法,所以在LGPerson元類的methods中就更加獲取不到,返回0x00

sayHappy類方法,所以存在于LGPerson元類的實例方法列表中.
1. method3, 會去找LGPerson元類的實例方法列表中尋找,恰好就存在sayHappy,所以method3有值
2. method4, 也是會在會在找LGPerson元類的實例方法列表中尋找,所以method4有值

接下來我們看class_getInstanceMethod的源碼.
從源碼得出:先從cls類的methods列表中查找是否存在sel,然后從cls類的父類的methods列表查找.... 直到從NSObject類的methods列表查找

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    return _class_getMethod(cls, sel);
}

static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }
    return m;
}
class_getInstanceMethod的源碼流程圖.png

sayHello是實例方法,存在于LGPerson類的methods列表中。
所以 method05有值,method6是0x00。

sayHappy是類方法,存在于LGPerson元類的methods列表中.
所以 method07是0x00,method8有值

【拓展】3.以下代碼輸出的結果是什么?
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end

@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end

@interface LGTeacher : LGPerson
- (void)say666;
@end

@implementation LGTeacher
- (void)say666{
    
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGTeacher *teacher = [LGTeacher alloc];
        Class pClass     = object_getClass(teacher);
        const char *className = class_getName(pClass);
        Class metaPClass = objc_getMetaClass(className);
        
        Method method1 = class_getClassMethod(pClass, @selector(sayHello));
        Method method2 = class_getClassMethod(metaPClass, @selector(sayHello));
        
        Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
        Method method4 = class_getClassMethod(metaPClass, @selector(sayHappy));
        
        Method method5 = class_getClassMethod(pClass, @selector(say666));
        Method method6 = class_getClassMethod(metaPClass, @selector(say666));
        LGLog(@"method1=%p",method1);
        LGLog(@"method2=%p",method2);
        LGLog(@"method3=%p",method3);
        LGLog(@"method4=%p",method4);
        LGLog(@"method5=%p",method5);
        LGLog(@"method6=%p",method6);
        
        Method method7 = class_getInstanceMethod(pClass, @selector(sayHello));
        Method method8 = class_getInstanceMethod(metaPClass, @selector(sayHello));
        
        Method method9 = class_getInstanceMethod(pClass, @selector(sayHappy));
        Method method10 = class_getInstanceMethod(metaPClass, @selector(sayHappy));
        
        Method method11 = class_getInstanceMethod(pClass, @selector(say666));
        Method method12 = class_getInstanceMethod(metaPClass, @selector(say666));
        
        LGLog(@"method5=%p",method7);
        LGLog(@"method6=%p",method8);
        LGLog(@"method7=%p",method9);
        LGLog(@"method8=%p",method10);
        LGLog(@"method7=%p",method11);
        LGLog(@"method8=%p",method12);
    }
     return 0;
}

首先sayHello是實例方法,存在于LGPerson類的methods列表中。

對于method1,class_getClassMethod(LGTeacher, @selector(sayHello)) ,
1. 首先去LGTeacher的元類里面找,沒有找到,
2. 接下來去LGTeacher的元類的superclass,即LGPerson的元類里面去找,沒有找到,
3. 之后去根元類NSObject類尋找,更是不會找到,返回0x00

對于method2 ,class_getClassMethod(LGTeacher的元類, @selector(sayHello)),
1. 因為傳入的是LGTeacher的元類,所以直接到LGTeacher的元類里面找,沒有找到,
2. 接下來去LGTeacher的元類的superclass,即LGPerson的元類里面去找,沒有找到,
3. 之后去根元類NSObject類尋找,更是不會找到,返回0x00。

首先sayHappy是類方法,存在于LGPerson元類的methods列表中。

對于method3 ,class_getClassMethod(LGTeacher, @selector(sayHappy)),
1. 首先去LGTeacher的元類里面實例方法列表中找,沒有找到,
2. 接下來去LGTeacher的元類的superclass,即LGPerson的元類里面的實例方法列表中尋找,可以找到,所以method3有值

對于method4來說 ,
1. 首先去LGTeacher的元類里面實例方法列表中找,沒有找到,
2. 接下來去LGTeacher的元類的superclass,即LGPerson的元類里面的實例方法列表中尋找,可以找到,所以method4有值

首先say666是實例方法,存在于LGTeacher類的實例方法列表中

所以method5 和 method6都為 0x00.

首先sayHello是實例方法,存在于LGPerson類的實例方法列表

對于method7 來說,
1. 去LGTeacher類的實例方法列表中找,沒有找到,
2. 然后去LGPerson類的實例方法列表中找,找到了,method7有值。

對于method8 ,
1. 去LGTeacher元類的實例方法列表中找,沒有找到,
2. 然后去LGPerson元類的實例方法列表中找,沒有找到,
3. 最后去根元類和NSObject類的實例方法列表尋找,沒有找到,method8=0x00

首先sayHappy是類方法,存在于LGPerson元類的實例方法列表中。

對與method9,
1. 去LGTeacher類的實例方法列表中找,沒有找到,
2. 然后去LGPerson類的實例方法列表中找,沒有找到了,3. 最后去NSObject類的實例方法列表尋找,沒有找到,method9=0x00

對與method10,
1. 去LGTeacher元類的實例方法列表中找,沒有找到,
2. 然后去LGPerson元類的實例方法列表中找,找到了,method10有值

首先say666是實例方法,存在于LGTeacher類的實例方法列表

所以method11有值,method12=0x00

**打印結果: **

method1=0x0
method2=0x0
method3=0x100003070
method4=0x100003070
method5=0x0
method6=0x0

method7=0x1000030d8
method8=0x0
method9=0x0
method10=0x100003070
method11=0x100003188
method12=0x0
4.下面的代碼輸出什么?
@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

結果: Son / Son

分析:

對于上面的答案,第一個的結果應該是我們的預期結果,但是第二個結果卻讓我們很費解了。

那我們利用前面文章講過的知識點來分析一下整個的流程。

因為,Son 及 Father 都沒有實現 -(Class)calss 方法,所以這里所有的調用最終都會找到基類 NSObject 中,并且在其中找到 -(Class)calss 方法。那我們需要了解的就是在 NSObject 中這個方法的實現了。

在 NSObject.mm 中可以找到 -(Class)class 的實現:

- (Class)class {
    return object_getClass(self);
}

在 objc_class.mm 中找到 object_getClass 的實現:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

ps:上面的方法定義可以去可編譯調試的objc4源碼中下載源碼哦。

可以看到,最終這個方法返回的是,調用這個方法的 objc 的 isa 指針。那我們只需要知道在題干中的代碼里面最終是誰在調用 -(Class)class 方法就可以找到答案了。

接下來,我們利用 clang -rewrite-objc 命令,將題干的代碼轉化為如下代碼:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_cgm28r0d0bz94xnnrr606rf40000gn_T_Car_3f2069_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_cgm28r0d0bz94xnnrr606rf40000gn_T_Car_3f2069_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Car"))}, sel_registerName("class"))));

從上方可以得出,調用 Father class 的時候,本質是在調用

objc_msgSendSuper(struct objc_super *super, SEL op, ...)

struct objc_super 的定義如下:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

從定義可以得知:當利用 super 調用方法時,只要編譯器看到super這個標志,就會讓當前對象去調用父類方法,本質還是當前對象在調用,是去父類找實現,super 僅僅是一個編譯指示器。但是消息的接收者 receiver 依然是self。最終在 NSObject 獲取 isa 指針的時候,獲取到的依舊是 self 的 isa,所以,我們得到的結果是:Son。

5. 看看下方的代碼會輸出什么?
@interface Father : NSObject
@end

@implementation Father

- (Class)class {
    return [Father class];
}

@end

---

@interface Son : Father
@end

@implementation Son

- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

@end

int main(int argc, const char * argv[]) {
    Son *foo = [[Son alloc]init];
    return 0;
}

---輸出:---
Father
Father

更多的面試題和答案:https://github.com/iOSputao/iOS-

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。