Runtime消息發(fā)送和消息轉(zhuǎn)發(fā)

runtime源碼下載

一、理解類對象

類對象
struct objc_class {
    Class _Nonnull isa  // 指向元類;

#if !__OBJC2__
    Class _Nullable super_class                             // 指向父類;
    const char * _Nonnull name                             // 類名;
    long version                                             // 類的版本信息;
    long info                                               
    long instance_size                                       // 類的實例變量大小;
    struct objc_ivar_list * _Nullable ivars                  // 成員變量列表;
    struct objc_method_list * _Nullable * _Nullable methodList  // 對象方法列表;
    struct objc_cache * _Nonnull cache                       // 最近使用的方法緩存,優(yōu)先在這里查找方法;
    struct objc_protocol_list * _Nullable protocols          // 協(xié)議列表;
#endif

} OBJC2_UNAVAILABLE;

1,objc對象isa 指針指向他的類對象。
2,類對象中也有一個isa指針指向它的元類(meta class),即類對象是元類的實例。
3,元類內(nèi)部存放的是類方法列表,isa指針指向根元類,根元類的isa指針指向自己,superclass指針指向NSObject類。
4,根對象就是NSObject,它的superclass指針指向nil。

二、消息發(fā)送

向?qū)ο髠鬟f消息,會使用動態(tài)綁定機制來決定決定需要調(diào)用的方法,在底層,所有方法都是C語言函數(shù),對象收到消息后,究竟調(diào)用那個方法取決于運行期,所以O(shè)bjective-C是一門真正的動態(tài)語言。

給對象發(fā)送消息,編輯器會將其轉(zhuǎn)換為一條C語言函數(shù)調(diào)用,objc_msgSend,原型:

void objc msgSend (id self, SEL cmd, ...)

第一個參數(shù)代表接收者,第二個參數(shù)代表選擇子(SEL 是選擇子的類型),后續(xù)參數(shù)就是消息中的那些參數(shù),其順序不變。

objc_msgSend 函數(shù)會依據(jù)接收者與選擇子的類型來調(diào)用適當(dāng)?shù)姆椒?。在接收者所屬的類中搜尋其“方法列表?list of methods),如果能找到與選擇子名稱相符的方法,就跳至其實現(xiàn)代碼。若是找不到,那就沿著繼承體系繼續(xù)向上查找,等找到合適的方法之后再跳轉(zhuǎn)。如果最終還是找不到相符的方法,那就執(zhí)行“消息轉(zhuǎn)發(fā)” (messags forwarding)操作。
這么說來,想調(diào)用一個方法似乎需要很多步驟。所幸 objc_ msgSend 會將匹配結(jié)果緩在“快速映射表”(ftast map)里面,每個類都有這樣一塊緩存,若是稍后還向該類發(fā)送與選擇子相同的消息,那么執(zhí)行起來就很快了。

這只是部分消息的調(diào)用過程,其他“邊界情況”(edge case)則需要交由 Objective-C 運行環(huán)境中的另一些函數(shù)來處理:

  • objc_msgSend_stret。如果待發(fā)送的消息要返回結(jié)構(gòu)體,那么可交由此函數(shù)處理。只有當(dāng)CPU 的寄存器能夠容納得下消息返回類型時,這個函數(shù)才能處理此消息。若是返回值無法容納于 CPU 寄存器中(比如說返回的結(jié)構(gòu)體太大了),那么就由另一個函數(shù)執(zhí)行派發(fā)。此時,那個函數(shù)會通過分配在棧上的某個變量來處理消息所返回的結(jié)構(gòu)體。
  • objc_msgSend_fpret。 如果消息返回的是浮點數(shù),那么可交由此函數(shù)處理。在某些架構(gòu)的 CPU 中調(diào)用函數(shù)時,需要對 “浮點數(shù)寄存器(foating-point register)做特殊處理,也就是說,通常所用的 objc_msgSend 在這種情況下并不合適。這個函數(shù)是為了處理
    ×86 等架構(gòu) CPU 中某些令人稍覺驚訝的奇怪狀況。
  • objc_ msgSendSuper。如果要給超類發(fā)消息,例如 [super message:parameter],那么就交由此函數(shù)處理。也有另外兩個與 objc_msgSend_ stret 和 objc_msgSend fpret 等效的函數(shù),用于處理發(fā)給 super 的相應(yīng)消息。

剛才提到,objc_msgSend 等函數(shù)一旦找到應(yīng)該調(diào)用的方法文現(xiàn)”之后,就會“跳轉(zhuǎn)過去”。之所以能這樣做,是因為 Objiective-C 對象的每個方法都可以視為簡單的C 函數(shù),其原型如下:
<return type> Class selector (id self, SEI _end, ...)
真正的函數(shù)名和上面寫的可能不太一樣,用 “類”(class)和“選擇子”(selector)來命名是想解釋其工作原理。每個類里都有一張表格,其中的指針都會指向這種C語言函數(shù),而選擇子的名稱則是查表時所用的“鍵”。objc_msgSend 等函數(shù)正是通過這張表格來尋找應(yīng)該執(zhí)行的方法并跳至其實現(xiàn)的。請注意,原型的樣子和 objc_msgSend函數(shù)很像。這不是巧合,而是為了利用 “尾調(diào)用優(yōu)化”(tail-call optimization)技術(shù),令“跳至方法實現(xiàn)” 這一操作變得更簡單些。

如果某兩數(shù)的最后一項操作是調(diào)用另外一個函數(shù),那么就可以運用 “尾調(diào)用優(yōu)化”技術(shù)。編譯器會生成調(diào)轉(zhuǎn)至另一兩數(shù)所需的指令碼,而且不會向調(diào)用堆棧中推人新的“棧幀” (frame stack)。只有當(dāng)某兩數(shù)的最后一個操作僅僅是調(diào)用其他兩數(shù)而不會將其返回值另作他用時,才能執(zhí)行一尾調(diào)用優(yōu)化”。這項優(yōu)化對 obje msgSend 非常關(guān)鍵,如果不這么做的話,那么每次調(diào)用 Objective-C 方法之前,都需要為調(diào)用 objc_ msgSend 函數(shù)準(zhǔn)備“棧幀”,大家在“棧蹤跡”(stack trace)中可以看到這種“棧幀”。此外,若是不優(yōu)化,還會過早地發(fā)生“棧溢出”(stack overflow)現(xiàn)象。

三、消息轉(zhuǎn)發(fā)

書籍:Effective Objective-C 2.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容