網上不少大神所解釋的 super 調用邏輯,實際上好像並不能說得通。這裏有個人一點點理解。c++
曾經有過一份特別好的runtime
習題,在孫源大神的博客裏神經病院objc runtime入院考試。題目很是難,也很深。其中的第一題,關於super
關鍵字也是很刷新認知。緩存
可是我的感受,他的解答和其餘給出詳細解釋的大神們的回答,都有點不太能說得通。因此在這裏說一下個人一些想法。函數
首先先把題目再放出來:ui
下面的代碼輸出什麼?this
@implementation Son : Father - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
答案是Son / Son
。孫源大神給的一個簡短解釋是這樣的:設計
由於super爲編譯器標示符,向super發送的消息被編譯成
objc_msgSendSuper
,但仍以self做爲reveiver。指針
這個解釋顯然不太夠,看了以後依然有點懵逼。由於,這個沒有說背後的理論證據,只給了一個結論性的理由。因此有另一個大神,在他的文章中詳細解釋了,這也是目前流傳最廣的一份答案,ChunYeah大佬的博客刨根問底 Objective-C Runtime(1)- Self & Super。調試
貼一下他給出的解答中的關鍵部分:code
當調用
[super class]
時,會轉換成objc_msgSendSuper
函數。第一步先構造objc_super
結構體,結構體第一個成員就是self
。第二個成員是(id)class_getSuperclass(objc_getClass(「Son」))
, 實際該函數輸出結果爲Father
。第二步是去Father
這個類裏去找- (Class)class
,沒有,而後去NSObject
類去找,找到了。最後內部是使用objc_msgSend(objc_super->receiver, @selector(class))
去調用,此時已經和[self class]
調用相同了,故上述輸出結果仍然返回 Son。對象
這個解釋中,有一個地方的跨度很是大,就是:
最後內部是使用
objc_msgSend(objc_super->receiver, @selector(class))
去調用
這個地方很是奇怪,就是原本一直都是調用的objc_msgSendSuper
函數,爲啥最後換了調用另外一個函數。單從文章全文來看,彷佛並無作出解釋。
個人猜想是,做者可能認爲, objc_msgSendSuper
函數在向父類中去找實現的時候,最後一直找到了NSObject
才找到了 - (Class)class
這個方法的實現,因而這個函數找到這個實現後,直接對這個類對象發送消息,變成了 objc_msgSend(objc_super->receiver, @selector(class))
。
也就是說,用objc_msgSendSuper
找方法實現,找到了,再經過objc_msgSend
給調用者發送消息,從新去沿着繼承鏈再找方法實現。這麼看來,豈不是很傻,每一次調用父類方法的時候,都要遍歷兩遍繼承鏈?雖然runtime
有很好的方法緩存機制,可是如此遍歷,確定不是一個好的設計。何況,objc_msgSendSuper
這個函數名就表示,就是已經在給父類發消息了,從代碼的調用上,也能夠證實。
下面就是調試代碼:
@interface Father : NSObject @end @implementation Father - (Class)class { return [NSValue class]; } @end @interface Son : Father - (void)superClassName; @end @implementation Son - (Class)class { return [NSNumber class]; } - (void)superClassName { NSLog(@"Son's super Class is : %@", [super class]); } @end int main(int argc, const char * argv[]) { Son *foo = [Son new]; [foo superClassName]; return 0; }
這段代碼就能夠很好的反映,到底是不是在調用super
方法時是否是發送兩次消息。
首先,在兩個類的class
重寫方法中,打斷點。
運行起來後,第一次的斷點,是走Father
類。由於此時其實是objc_msgSendSuper
在發送消息。繼續運行,Son
的斷點是根本不會走的。會直接輸出Son's super Class is : NSValue
。
因此,爲何當父類重寫- (Class)class
方法時,輸出就是正確的?
按照ChunYeal大佬的說法,找到方法後會變成使用 objc_msgSend(objc_super->receiver, @selector(class))
去調用,那麼仍是輸出了 Son 的類纔對啊。
因此,這個例子就證實了兩點:
沿着父類找實現的過程只有一次。
使用super
關鍵字究竟能不能獲得正確結果,取決因而不是調用的方法是由NSObject
來實現的。
因此,能夠推斷,在原先題目中,之因此Son
取得super class
的結果依然是Son
,是由於NSObject
的實現緣由。
因此思路清晰了,從新捋一下。原來題目中整個過程應該是這樣的
Son 對象調用 super class
方法,編譯器轉換爲objc_msgSendSuper
函數發送class
消息。
函數直接前往 Father 的實現中尋找,發現並無實現。
繼續沿着繼承鏈,找到了 NSObject
,有class
方法實現。
直接返回結果給調用者,這個結果就是Son Class
。
因此,咱們就須要明確,NSObject
類的class
方法實現是怎樣的原理。
因此,咱們能夠查看源文件NSObject.mm
能夠看到:
- (Class)class { return object_getClass(self); }
繼續看:
Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; }
立刻接近真相:
objc_object::getIsa() { if (!isTaggedPointer()) return ISA(); uintptr_t ptr = (uintptr_t)this; if (isExtTaggedPointer()) { uintptr_t slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK; return objc_tag_ext_classes[slot]; } else { uintptr_t slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK; return objc_tag_classes[slot]; } }
再往下已經不須要看了,由於咱們能明確,實際上,NSObject
的- (Class)class
方法實際的本質是取得isa
指針。咱們前面已經知道,當使用super
關鍵字時,會轉換成函數
objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一個參數是:
struct objc_super { __unsafe_unretained id receiver; __unsafe_unretained Class super_class; };
使用clang
重寫文件,查看NSLog(@"%@", NSStringFromClass([super class]));
對於的代碼:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));
因此能夠獲得結論,在向父類傳遞消息的時候,是去父類找實現,可是消息的接收者receiver
依然是self
。這一點,孫源大神講得的確正確。可是關鍵就在於:
真正使得例子中返回的class
爲 Son 的緣由,在於返回的是self
的isa
。
向上尋找的過程當中,並不在意是具體的哪個父類實現了方法。而到到了NSObject
中,也沒有辦法真的知道這個消息的接受者到底是經過什麼方式來訪問isa
的,因此,做爲基類,就直接返回消息接受者的信息,這個在設計上就避免了不少沒必要要的問題。
因此,我認爲,個人這個解釋更具備合理性一些。
但願有不一樣意見的大神能指正其中的錯誤,更深刻完全的理解這個問題。
文章後續有更新,歡迎訪問個人博客http://suntao.me查看