最近小編所在公司招 iOS 開發職位,小編也出了幾道面試題考察下候選人的 iOS 開發水平,其中有一道題如下:
@implementation Student : Person
- (instancetype)init
{
self = [super init];
if (self) {
id obj1 = [self class];
id obj2 = [super class];
NSLog(@"%@",obj1);
NSLog(@"%@",obj2);
}
return self;
}
@end
複製程式碼
大部分候選人回答的 [self class ]輸出 Student , [Super class]輸出 Person, 只有少部分候選人回答都是輸出 Student ,當然至於為什麼輸出結果都是 Student, 很少有能回答出來的.
接下來小編通過將Student.m 轉換成 Cpp 檔案,帶大家一塊去看看 [self class] 和 [super class] 背後究竟做了那些事情.
通過終端講Student.m 轉成Student.cpp 檔案
2 找到 Student 的 init 方法 分析程式碼
static instancetype _I_Student_init(Student * self, SEL _cmd) {
self = ((Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"));
if (self) {
id obj1 = ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"));
id obj2 = ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hj_pwgsq9614nb0vq4zd315tcx80000gn_T_Student_e7cbc1_mi_0,obj1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hj_pwgsq9614nb0vq4zd315tcx80000gn_T_Student_e7cbc1_mi_1,obj2);
}
return self;
}
複製程式碼
由此可見 當我們呼叫[self class] 時候實際上編譯器最終給我們轉成 objc_msgSend(self,@selector(class)) , 訊息的接收者是當前所在類的例項物件 , 這個時候就會去 self 所在類 Student 去查詢 class 方法 , 如果當前類 Student 沒有 class 會向Student 父類 Person 類找 class 方法, 如果 Person 類也沒有找到 class 方法,最終會找到最頂級父類 NSObject 的 class 方法, 最終找到 NSObject 的 class 方法 ,並呼叫了object_getClass(self) ,由於訊息接收者是 self 當前類例項物件, 所以最終 [self class]輸出 Student
那麼為什麼明明呼叫了 super 這個關鍵字 返回的[super class] 還是 Student 呢 ?
通過上邊程式碼可知 , [super class] 最終編譯器轉化成了 objc_msgSendSuper(struct objc_super *,SEL) ,其中
/// 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 */
};
#endif
複製程式碼
objc_super 是一個結構體,內部有一個 receiver 例項,和一 個 Class super_class,指向了當前類的父類 Class ,通過這個父類可以直接的從父類裡邊開始查詢方法,由於訊息接收者還是當前類的例項物件 self, 最終如果在父類中沒有找到class 這個方法,會在 Person 類的父類 NSObject 中去查詢 class 方法,由於 NSObject 的 class 方法,呼叫的是 object_getClass(self) ,所以最終訊息接收者為 student 例項物件,所以返回的還是 Student .
當然我們知道了[ super class] 最終編譯後程式碼樣子,也可以自己手動去呼叫 objc_msgSendSuper 方法
- (instancetype)init
{
self = [super init];
if (self) {
id obj1 = [self class];
id obj2 = [super class];
NSLog(@"%@",obj1);
NSLog(@"%@",obj2);
struct objc_super1 {
__unsafe_unretained id receiver;
Class superClass;
};
struct objc_super1 obj_super = {self,class_getSuperclass(object_getClass(self))};
id obj3 = objc_msgSendSuper(&obj_super,@selector(class));
}
return self;
}
複製程式碼
obj3輸出的結果和直接呼叫 [super class]結果是一模一樣的,剛才我們假設的情況都是 super Person 沒有 class 方法,如果 Person 重寫了 class 方法呢,將會怎麼樣?
當我們在 Student init 方法中呼叫 [super class] 時候 ,它首先會到 Person 類中查詢 class 方法 ,當它發現了 Person 實現了 class 方法,就會呼叫[person class] 方法, object_getClass 這個時候 object_getClass(self), 這個 self 是 Student 的例項物件,就是訊息接收者,所以即使重寫了 Person 的 class 方法 ,依然返回的還是 Student ,除非來個極端的 把 class 方法 實現 返回個 nil , 這樣最終呼叫[super class]結果才會返回nil
KVO巧妙的利用了子類 重寫了 class 方法 ,讓我們誤以為 [person class] 還是當前類 Person ,而不是動態建立的子類 NSKVONotifily_Person 這個子類 , 就是讓子類返回了父類的 Class ,所以呼叫 [person class] 返回的還是 Person 從而對開發者隱藏了 NSKVONotifily_Person子類的存在
- (Class)class {
return class_getSuperclass(object_getClass(self));
}
複製程式碼