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

師大小海騰發表於2020-02-25

Runtime 系列文章

深入淺出 Runtime(一):初識
深入淺出 Runtime(二):資料結構
深入淺出 Runtime(三):訊息機制
深入淺出 Runtime(四):super 的本質
深入淺出 Runtime(五):具體應用
深入淺出 Runtime(六):相關面試題

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

1. objc_super 與 objc_msgSendSuper

我們先來看兩個資料結構objc_superobjc_super2
它們的區別在於第二個成員:
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_super

// 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++ 程式碼:

    [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_superSEL

LLVM & 中間程式碼

那麼為什麼前面說super會轉換為objc_msgSendSuper2()函式的呼叫呢? 因為轉成的 C++ 的實現和真正的底層實現是有差異的,
LLVM編譯器會將“ OC 程式碼”先轉成“中間程式碼(.ll)”再轉成“彙編、機器程式碼”,該中間程式碼非 C/C++。
可以使用以下命令列指令生成中間程式碼:clang -emit-llvm -S main.m
具體可以檢視官方文件 LLVM,這裡不做過多介紹。

通過彙編驗證

將 ViewController.m 檔案轉換成彙編程式碼進行驗證:

深入淺出 Runtime(四):super 的本質
檢視第 18 行程式碼即[super viewDidLoad]轉換成的彙編程式碼

深入淺出 Runtime(四):super 的本質
以上可以看到,[super viewDidLoad]底層實際上是轉換成了objc_msgSendSuper2()函式的呼叫而非objc_msgSendSuper()

super 本質

  • 當使用 super 呼叫方法的時候,底層會轉換為objc_msgSendSuper2()函式的呼叫,該函式接收兩個引數struct objc_super2SEL
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相同,所以它們的返回值是一樣的。

相關文章