個人對於super的呼叫過程中,一些不一樣的理解

2021-09-16 22:41:46 字數 4776 閱讀 2847

網上很多大神所解釋的 super 呼叫邏輯,實際上好像並不能說得通。這裡有我的一點點理解。

曾經有過乙份特別好的runtime習題,在孫源大神的部落格裡神經病院objc runtime入院考試。題目非常難,也很深。其中的第一題,關於super關鍵字也是很重新整理認知。

但是個人感覺,他的解答和其他給出詳細解釋的大神們的回答,都有點不太能說得通。所以在這裡說一下我的一些想法。

首先先把題目再放出來:

下面的**輸出什麼?

@implementation son : father

- (id)init

return self;

}@end

答案是son / son。孫源大神給的乙個簡短解釋是這樣的:

因為super為編譯器標示符,向super傳送的訊息被編譯成objc_msgsendsuper,但仍以self作為reveiver。

這個解釋顯然不太夠,看了之後依然有點懵逼。因為,這個沒有說背後的理論證據,只給了乙個結論性的理由。所以有另外乙個大神,在他的文章中詳細解釋了,這也是目前流傳最廣的乙份答案,chunyeah大佬的部落格刨根問底 objective-c runtime(1)- self & super。

貼一下他給出的解答中的關鍵部分:

當呼叫[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這個函式名就表示,就是已經在給父類發訊息了,從**的呼叫上,也可以證明。

下面就是除錯**:

@inte***ce father : nsobject

@end

@implementation father

- (class)class

@end

@inte***ce son : father

- (void)superclassname;

@end

@implementation son

- (class)class

- (void)superclassname

@end

int main(int argc, const char * ar**)

這段**就可以很好的反映,究竟是不是在呼叫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
繼續看:

class object_getclass(id obj)

馬上接近真相:

objc_object::getisa() 

else

}

再往下已經不需要看了,因為我們能明確,實際上,nsobject- (class)class方法實際的本質是取得isa指標。我們前面已經知道,當使用super關鍵字時,會轉換成函式

objc_msgsendsuper(struct objc_super *super, sel op, ...)
第乙個引數是:

struct objc_super ;
使用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), sel_registername("class"))));
所以可以得到結論,在向父類傳遞訊息的時候,是去父類找實現,但是訊息的接收者receiver依然是self。這一點,孫源大神講得的確正確。但是關鍵就在於:

真正使得例子中返回的class為 son 的原因,在於返回的是selfisa

向上尋找的過程中,並不在乎是具體的哪乙個父類實現了方法。而到到了nsobject中,也沒有辦法真的知道這個訊息的接受者究竟是通過什麼方式來訪問isa的,所以,作為基類,就直接返回訊息接受者的資訊,這個在設計上就避免了很多不必要的問題。

所以,我認為,我的這個解釋更具有合理性一些。

希望有不同意見的大神能指正其中的錯誤,更深入徹底的理解這個問題。

函式遞迴呼叫過程中的呼叫堆疊的情況

為了加深對函式遞迴呼叫過程中的理解,本demo程式特意在vs2008 c 控制台程式實現了階乘的計算功能,用於觀察函式遞迴呼叫過程中的呼叫堆疊的情況。原始碼如下 using system using system.collections.generic using system.linq using...

python介面呼叫過程中的小總結

導包 import requests import json 在介面呼叫過程中 程式執行 出現如下錯誤 問題1 httpsconnectionpool host z.jd.com port 443 需要取消認證 在requests請求引數中新增 verify false引數。如 requests.g...

Dubbo發布過程中,消費者呼叫過程

目錄2.遠端物件rpcinvocation 我們從controller開始看起,當我們發起乙個request請求的時候,controller呼叫的是service,此處我們呼叫的dubbo引用服務的 類 iuserservice是乙個介面,實際呼叫的是乙個 類,我們接著往下看 restcontrol...