Runtime 系列文章
深入淺出 Runtime(一):初識
深入淺出 Runtime(二):資料結構
深入淺出 Runtime(三):訊息機制
深入淺出 Runtime(四):super 的本質
深入淺出 Runtime(五):具體應用
深入淺出 Runtime(六):相關面試題
1. objc_super 與 objc_msgSendSuper
我們先來看兩個資料結構objc_super
和objc_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_super
和SEL
。
LLVM & 中間程式碼
那麼為什麼前面說super
會轉換為objc_msgSendSuper2()
函式的呼叫呢?
因為轉成的 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_class
的superclass
指標拿到它的父類,從父類開始查詢方法的實現。忽略“從 receiverClass 中查詢方法的過程”,對應下圖就是直接從第 5 步開始。- 要注意
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
class
和superclass
方法的實現在 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
相同,所以它們的返回值是一樣的。