深刻淺出 Runtime(四):super 的本質

Runtime 系列文章

深刻淺出 Runtime(一):初識
深刻淺出 Runtime(二):數據結構
深刻淺出 Runtime(三):消息機制
深刻淺出 Runtime(四):super 的本質
深刻淺出 Runtime(五):相關面試題html

1. objc_super 與 objc_msgSendSuper

咱們先來看兩個數據結構objc_superobjc_super2面試

它們的區別在於第二個成員:markdown

  • objc_super:super_class // receiverClass 的父類
  • objc_super2:current_class // receiverClass(消息接收者的class對象)
// message.h(objc4)
struct objc_super {
    __unsafe_unretained _Nonnull id receiver;  // 消息接收者
#if !defined(__cplusplus) && !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;  // receiverClass 的父類
#endif
    /* super_class is the first class to search */
};

// objc_runtime_new.h(objc4)
struct objc_super2 {
    id receiver;  // 消息接收者
    Class current_class;  // receiverClass(消息接收者的class對象)
};
複製代碼

再來看兩個函數objc_msgSendSuper()objc_msgSendSuper2()數據結構

從源碼來看,兩個函數所接收的參數沒有區別。函數

可是從官方註釋咱們能夠推測,objc_msgSendSuper2() 函數所接收的第一個參數應該爲objc_super2而非objc_superoop

// message.h(objc4)
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

// objc-abi.h(objc4)
// objc_msgSendSuper2() takes the current search class, not its superclass.
id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
複製代碼

2. self 和 super

self

  • OC 方法都帶有兩個隱式參數:(id)self(SEL)_cmd
  • self 是一個對象指針,指向當前方法的調用者/消息接收者;
    • 若是是實例方法,它就是指向當前類的實例對象;
    • 若是是類方法,它就是指向當前類的類對象。
  • 當使用 self 調用方法的時候,底層會轉換爲objc_msgSend()函數的調用,經過上一篇文章能夠知道,該函數會從當前消息接收者類中開始查找方法的實現。

super

  • super 是一個編譯器指令;
  • 當使用 super 調用方法的時候,底層會轉換爲objc_msgSendSuper2()函數的調用,該函數會從當前消息接受者類的父類中開始查找方法的實現。

3. super 本質

咱們經過 clang 將如下 OC 代碼 轉換爲 C++ 代碼:post

[super viewDidLoad];
複製代碼
// 轉換爲 C++
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    // 簡化
    struct objc_super arg = {
        self,
        class_getSuperclass(objc_getClass("ViewController"))
    };
    objc_msgSendSuper(arg, sel_registerName("viewDidLoad"));
複製代碼

能夠看到,Runtime 將super轉換爲objc_msgSendSuper()函數的調用,參數爲objc_superSELui

LLVM & 中間代碼

那麼爲何前面說super會轉換爲objc_msgSendSuper2()函數的調用呢?spa

由於轉成的 C++ 的實現和真正的底層實現是有差別的,命令行

LLVM編譯器會將 「 OC 代碼」 先轉成 「中間代碼(.ll)」 再轉成 「彙編、機器代碼」,該中間代碼非 C/C++。

可使用如下命令行指令生成中間代碼:clang -emit-llvm -S main.m
具體能夠查看官方文檔 LLVM,這裏不作過多介紹。

經過彙編驗證

將 ViewController.m 文件轉換成彙編代碼進行驗證:

查看第 18 行代碼即[super viewDidLoad]轉換成的彙編代碼

以上能夠看到,[super viewDidLoad]底層其實是轉換成了objc_msgSendSuper2()函數的調用而非objc_msgSendSuper()

super 本質

當使用 super 調用方法的時候,底層會轉換爲objc_msgSendSuper2()函數的調用,該函數接收兩個參數struct objc_super2 SEL

struct objc_super2 {
    id receiver;  // 消息接收者
    Class current_class;  // receiverClass
};
id _Nullable objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
複製代碼

objc_msgSendSuper2()函數內部會經過current_classsuperclass指針拿到它的父類,從父類開始查找方法的實現。忽略「從 receiverClass 中查找方法的過程」,對應下圖就是直接從第 5 步開始。

objc_msgSendSuper2()執行流程

要注意receiver消息接收者仍是子類對象,而不是父類對象,只是查找方法實現的範圍變了。

4. 相關面試題

Q:調用如下 init 方法的打印結果是什麼?

@interface HTPerson : NSObject
@end

@interface HTStudent : HTPerson
@end

@implementation HTStudent
- (instancetype)init
{
    if (self = [super init]) {
        
        NSLog(@"[self class] = %@",[self class]);
        NSLog(@"[super class] = %@",[super class]);
        NSLog(@"[self superclass] = %@",[self superclass]);
        NSLog(@"[super superclass] = %@",[super superclass]);
        
    }
    return self;
}
@end
複製代碼

[self class] = HTStudent
[super class] = HTStudent
[self superclass] = HTPerson
[super superclass] = HTPerson

classsuperclass方法的實如今 NSObject 類中,能夠看到它們的返回值取決於receiver

+ (Class)class {
    return self;
}
- (Class)class {
    return object_getClass(self);
}
+ (Class)superclass {
    return self->superclass;
}
- (Class)superclass {
    return [self class]->superclass;
}
複製代碼

[self class]是從receiverClass開始查找方法的實現,若是沒有重寫的狀況,則會一直找到基類 NSObject,而後調用。

[super class]是從receiverClass->superclass開始查找方法的實現,若是沒有重寫的狀況,則會一直找到基類 NSObject,而後調用。

因爲receiver相同,因此它們的返回值是同樣的。

相關文章
相關標籤/搜索