Super @interface Super : NSObject @end @implementation Super (void)log{ NSLog(@"super"); } @end Child @interface Child : Super @end @implementation Ch ...
Super
@interface Super : NSObject
@end
@implementation Super
- (void)log{
NSLog(@"super");
}
@end
Child
@interface Child : Super
@end
@implementation Child
- (void)log{
if([super respondsToSelector:@selector(log)]){
[super performSelector:@selector(log)];
}
}
@end
Child中的log調用能成功嗎?
這種情況可能很多朋友都遇到過,在重寫父類的過程中,想調用一個父類沒有公開的方法,可能最省事的辦法就是使用performSelector了(當然這是不好的設計方式),直覺上覺得這樣當然沒問題啦,可是一運行卻發現代碼會陷入死迴圈,就拿上面的例子來說會發現一直在遞歸調用Child里的log方法,why???
要搞清楚這個問題,還需要從runtime入手,首先,讓我們來看看super是怎麼回事?
讓我們重寫下我們的objc代碼,可以看到如下的代碼片段:
((id (*)(__rw_objc_super *, SEL, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Child"))}, sel_registerName("performSelector:"), sel_registerName("log"));
是的,super最終會變成對objc_msgSendSuper方法的調用,而關於objc_msgSendSuper的文檔是這樣寫的:
/**
* 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
*/
而它的第一個參數定義大致如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained Class super_class;
/* super_class is the first class to search */
};
所以,super的作用其實可以理解為兩個:
- 告訴runtime從父類開始找SEL的實現
再說第二個作用之前,需要看看一個實例方法對應的IMP是什麼樣子的,還是看看之前重寫後的Child上的log的IMP:
static void _I_Child_log(Child * self, SEL _cmd) {...}
可見,在調用最終的c函數時,runtime會把當前實例作為第一個參數傳遞進去(這與很多其他面向對象的語言行為類似)。而super的第二個作用則正是要告訴runtime在執行第一步找到的IMP時self應該傳什麼,結合前面的內容,可以看到其實傳遞的就是當前的實例(這樣才能保證多態)。
然後我們再來看看performSelector的實現:
- (id)performSelector:(SEL)sel {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}
繞了一圈,[super performSelector:sel]
其實就是在執行objc_msgSend(self, sel)
, 因此對於之前出現的現象也就不會覺得奇怪了。