Runtime學習:面試題狙擊

前面兩篇文章分別記錄了自己學習 Runtime 的一些知識點以及常見的一些應用。之前立下 flag 說準備寫三篇關於 Runtime 的文章,於是就有了這篇文章。

題目一:下面的代碼輸出什麼?

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

結果: 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:上面的方法定義可以去官方OpenSource中下載源碼哦。

可以看到,最終這個方法返回的是,調用這個方法的 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。

擴展一下: 看看下方的代碼會輸出什麼?

@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

題目二:以下的代碼會輸出什麼結果?

@interface Sark : NSObject
@end
@implementation Sark
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        NSLog(@"%@", [NSObject class]);
        NSLog(@"%@", [Sark class]);

        BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
        BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
        NSLog(@"%d--%d--%d--%d", res1, res2, res3, res4);
    }
    return 0;
}

結果: 1--0--0--0

分析:

首先,我們先去查看一下題幹中兩個方法的源碼:

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

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

可以得知:

  • isKindOfClass 的執行過程是拿到自己的 isa 指針和自己比較,若不等則繼續取 isa 指針所指的 super class 進行比較。如此循環。
  • isMemberOfClass 是拿到自己的 isa 指針和自己比較,是否相等。
  1. [NSObject class] 執行完之後調用 isKindOfClass,第一次判斷先判斷 NSObject 和 NSObject 的 meta class 是否相等,之前講到 meta class 的時候放了一張很詳細的圖,從圖上我們也可以看出,NSObject 的 meta class 與本身不等。接着第二次循環判斷 NSObject 與meta class 的 superclass 是否相等。還是從那張圖上面我們可以看到:Root class(meta) 的 superclass 就是 Root class(class),也就是 NSObject 本身。所以第二次循環相等,於是第一行 res1 輸出應該爲YES。

  2. isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等。

  3. [Sark class] 執行完之後調用 isKindOfClass,第一次 for 循環,Sark 的 Meta Class 與 [Sark class] 不等,第二次 for 循環,Sark Meta Class 的 super class 指向的是 NSObject Meta Class, 和 Sark Class 不相等。第三次 for 循環,NSObject Meta Class 的 super class 指向的是 NSObject Class,和 Sark Class 不相等。第四次循環,NSObject Class 的super class 指向 nil, 和 Sark Class 不相等。第四次循環之後,退出循環,所以第三行的 res3 輸出爲 NO。

  4. isa 指向 Sark 的 Meta Class,和 Sark Class 也不等。

想要了解和閱讀更多的iOS面試題,可以添加QQ羣:763164022 !羣內有已經整理好的面試題以及答案文檔,同時,也希望大家一起多多交流技術,行情等等!歡迎你的加入~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章