Runtime原理探究(一)—— isa的深刻體會(蘋果對isa的優化)面試
Runtime原理探究(二)—— Class結構的深刻分析緩存
Runtime原理探究(三)—— OC Class的方法緩存cache_tmarkdown
Runtime原理探究(四)—— 刨根問底消息機制iphone
Runtime原理探究(六)—— 面試題中的Runtime函數
請看以下代碼post
****************************CLPerson.h
@interface CLPerson : NSObject
@end
****************************CLStudent.h
@interface CLStudent : CLPerson
@end
****************************CLStudent.m
@implementation CLStudent
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"[self class] = %@",[self class]);
NSLog(@"[self superclass] = %@",[self superclass]);
NSLog(@"[super class] = %@",[super class]);
NSLog(@"[super superclass] = %@",[super superclass]);
}
return self;
}
@end
****************************main.m
int main(int argc, char * argv[]) {
@autoreleasepool {
[[CLStudent alloc] init];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
請問調用[[CLStudent alloc] init];
會有什麼打印結果。優化
CLStudent
[self class]
—— 這個應該沒有什麼疑問,結果應該是[self class] = CLStudent
[self superclass]
—— 這個應該沒有什麼疑問,結果應該是[self superclass] = CLPerson
[super class]
—— 咱們重寫父類方法的時候,若是須要執行父類方法的邏輯,一般會加一句[super 方法名]
,那麼super
不是指向父類的指針呢,若是是的話,這裏的打印結果應該是[super class] = CLPerson
[super superclass]
—— 根據上面的推斷,那麼這裏應該是打印CLPerson
的父類,也就是[super superclass] = NSObject
ui實際打印結果
2019-08-12 20:59:23.580048+0800 iOS-Runtime[25274:2862267] [self class] = CLStudent 2019-08-12 20:59:23.580700+0800 iOS-Runtime[25274:2862267] [self superclass] = CLPerson 2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super class] = CLStudent 2019-08-12 20:59:23.580882+0800 iOS-Runtime[25274:2862267] [super superclass] = CLPerson 複製代碼
經過實際的代碼調試,咱們看到[super class]
和[super superclass]
的打印結果不是咱們所預期的。怎麼回事呢?spa
要解釋上面的問題,咱們仍是要回到問題的本質去探討。首先咱們簡化一下代碼
****************************CLPerson.h
@implementation CLStudent
- (void)run;
@end
****************************CLPerson.m
@implementation CLStudent
- (void)run {
NSLog(@"CLPerson Run");
}
@end
****************************CLStudent.m
@implementation CLStudent
- (void)run {
[super run];
NSLog(@"CLStudent Run");
}
@end
複製代碼
咱們在命令行窗口經過命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CLStudent.m -o CLStudent.cpp
拿到CLStudent.m
編譯後的中間代碼CLStudent.cpp
在其中能夠看到run
方法的底層實現以下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {
//[super run];
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("CLStudent"))}, sel_registerName("run"));
//NSLog(@"CLStudent Run");
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_CLStudent_5be081_mi_0);
}
*************🔧🔧🔧簡化一下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {
//[super run];
objc_msgSendSuper((__rw_objc_super){
(id)self,
(id)class_getSuperclass(objc_getClass("CLStudent"))
},
@selector(run));
//NSLog(@"CLStudent Run");不重要,不用管
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_CLStudent_5be081_mi_0);
}
*************♥️♥️♥️再精簡一下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {
//⚠️⚠️⚠️結構體單獨抽出來
struct __rw_objc_super arg = {
(id)self,
(id)class_getSuperclass(objc_getClass("CLStudent"))
};
//🌞🌞🌞[super run];
objc_msgSendSuper(arg, @selector(run));
}
複製代碼
從精簡後的代碼,能夠看出來,[super run];
底層其實是調用了函數objc_msgSendSuper(arg, @selector(run));
,首先咱們來分析一下它的兩個參數。第二個參數相信不用多解釋了,就是一個方法選擇器SEL
,重點看一下第一個參數,這是一個結構體__rw_objc_super
,咱們在當前的中間代碼裏面就能夠找到其定義,或者,你也能夠經過objc_super
在objc源碼裏面找到它的定義,以下所示
//♥️♥️♥️C++中間代碼裏的定義
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
//⚠️⚠️⚠️objc源碼中的定義
/// Specifies the superclass of an instance.
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 */
};
複製代碼
該結構題裏面其實就兩個成員變量
id receiver;
—— 消息接受者,其實實參傳遞的就是self
,也就是CLStudent
的實例對象Class super_class;
—— 父類,經過中間碼裏面該結構體初始化的賦值代碼(id)class_getSuperclass(objc_getClass("CLStudent")
能夠看出,這個父類就是CLStudent
的父類類對象[CLPerson class]
.接着咱們在看一下objc_msgSendSuper
裏面究竟幹了什麼事情。根據函數名字,咱們能夠在message.h
裏面找到相關的申明
/** * Sends a message with a simple return value to the superclass of an instance of a class. * * @param super A pointer to an \c objc_super data structure. Pass values identifying the * context the message was sent to, including the instance of the class that is to receive the * message and the superclass at which to start searching for the method implementation. * @param op A pointer of type SEL. Pass the selector of the method that will handle the message. * @param ... * A variable argument list containing the arguments to the method. * * @return The return value of the method identified by \e op. * * @see objc_msgSend */
OBJC_EXPORT id _Nullable objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
複製代碼
解讀一下注釋裏面對於參數的說明
super
—— 是一個指向結構體指針struct objc_super *
,它裏面的內容是{消息接受者 recv
, 消息接受者的父類類對象 [[recv superclass] class]
},objc_msgSendSuper
會將消息接受者的父類類對象
做爲消息查找的起點。op
—— 消息接受者接受的消息/方法,也就是須要去查找的方法爲了加深理解,咱們對比一下給對象正常發送消息後的查找流程
[obj message]
->在obj的類對象cls查找方法
->在cls的父類對象[cls superclass]查找方法
->在更上層的父類對象查找方法
-> ... ->在根類類對象 NSObject裏查找方法
[super message]
->->在obj的類對象cls查找方法(跳過此步驟)
(直接從這一步開始)在cls的父類對象[cls superclass]查找方法
->在更上層的父類對象查找方法
-> ... ->在根類類對象 NSObject裏查找方法
所以對於開頭的面試題,真正的運行邏輯以下
NSLog(@"[self class] = %@",[self class]);
消息接受者:
CLStudent
的實例對象最終調用的方法:基類
NSObject
的-(Class)class
方法2019-08-12 20:59:23.580048+0800 iOS-Runtime[25274:2862267] [self class] = CLStudent 複製代碼
NSLog(@"[super class] = %@",[super class]);
消息接受者:仍然是
CLStudent
的實例對象最終調用的方法:基類
NSObject
的-(Class)class
方法2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super class] = CLStudent 複製代碼
NSLog(@"[self superclass] = %@",[self superclass]);
消息接受者:
CLStudent
的實例對象最終調用的方法:基類
NSObject
的-(Class)superclass
方法2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [self superclass] = CLPerson 複製代碼
NSLog(@"[super superclass] = %@",[super superclass]);
消息接受者:仍然是
CLStudent
的實例對象最終調用的方法:基類
NSObject
的-(Class)superclass
方法2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super superclass] = CLPerson 複製代碼
由於-class
和-superclass
方法底層實際上裏面是使用了Runtime的API,簡單來講就是以下
- (Class)class
{
return object_getClass(self);
}
- (Class)superclass
{
return class_getSuperclass(object_getClass(self));
}
複製代碼
因此只要明白誰是真正的消息接受者,相信最後的打印結果就很容易理解了。
OK,super的本質探討就到這裏。
Runtime原理探究(一)—— isa的深刻體會(蘋果對isa的優化)