Runtime
Runtime 就是去解決如何在運行時期找到調用方法。
(OC 是一門動態語言,函數調用變成了消息發送,在編譯期不能知道要調用哪個函數)
對于實例變量有如下的思路:
instance -> class -> method -> SEL -> IMP -> 實現函數
實例對象 中存放 isa 指針 以及 實例變量。
有 isa 指針 可以找到 實例對象 所屬的類對象 (類也是對象,面向對象中一切都是對象)。
- 類 中存放著 實例方法列表,在這個 方法列表 中 SEL 作為 key,IMP 作為 value。(在編譯時期,根據方法名字會生成一個唯一的 Int 標識,這個標識就是 SEL。IMP 其實就是函數指針 指向了最終的函數實現。)
- 整個 Runtime 的核心就是 objc_msgSend 函數,通過給類發送 SEL 以傳遞消息,找到匹配的 IMP 再獲取最終的實現。
- 類中的 super_class 指針 可以追溯 整個繼承鏈。
- metaClass是元類,也有 isa 指針、super_class 指針。其中保存了 類方法列表。
重點:
- 向一個對象發送消息時,Runtime 會根據 實例對象 的 isa 指針 找到 其所屬的類,并自底向上直至根類(NSObject)中 去尋找 SEL 所對應的方法,找到后就運行整個方法。
SEL 與 IMP
SEL 可以將其理解為方法的 ID。
IMP 可以理解為函數指針,指向了最終的實現。
- others
OC 中不支持函數重載的原因:就是因為一個類的方法列表中不能存在兩個相同的 SEL 。
但是多個方法卻可以在不同的類中有一個相同的 SEL,不同類的實例對象執行相同的 SEL 時,會在各自的方法列表中去根據 SEL 去尋找自己對應的IMP,這使得OC可以支持函數重寫。
消息傳遞機制
- objc_msgSend函數的消息處理過程
- 不涵蓋消息cache機制
- 需要對Objective-C runtime有一定的了解
如下用于描述 objc_msgSend 函數的調用流程:
1.檢測 SEL 是否應該被忽略;
2.檢測發送的 target 是否為 nil ,如果是則忽略該消息;
- 當調用實例方法時,通過 isa 指針找到實例對應的 class 并且在其中的緩存方法列表以及方法列表中進行查詢,如果找不到則根據 super_class 指針在父類中查詢,直至根類(NSObject 或 NSProxy).
- 當調用類方法時,通過 isa 指針找到實例對應的 metaclass 并且在其中的緩存方法列表以及方法列表中進行查詢,如果找不到則根據 super_class 指針在父類中查詢,直至根類(NSObject 或 NSProxy). (根據此前的開篇中的圖,Root Meta Class 還是有根類的。)
- 如果還沒找到則進入消息動態解析過程。
動態消息解析過程
如下用于描述動態消息解析的流程:
通過 resolveInstanceMethod 得知方法是否為動態添加,YES則通過 class_addMethod 動態添加方法,處理消息,否則進入下一步。(dynamic 屬性就與這個過程有關,當一個屬性聲明為 dynamic 時 就是告訴編譯器:開發者一定會添加 setter/getter 的實現,而編譯時不用自動生成。)
這步會進入 forwardingTargetForSelector 用于指定哪個對象來響應消息。如果返回nil 則進入第三步。(這種方式把消息原封不動地轉發給目標對象,有著比較高的效率。如果不能自己的類里面找到替代方法,可以重載這個方法,然后把消息轉給其他的對象。)
這步調用 methodSignatureForSelector 進行方法簽名,這可以將函數的參數類型和返回值封裝。(如果返回 nil 說明消息無法處理并報錯 unrecognized selector sent to instance,如果返回 methodSignature,則進入 forwardInvocation )在forwardInvocation這里可以修改實現方法,修改響應對象等(如果方法調用成功,則結束。如果依然不能正確響應消息,則報錯 unrecognized selector sent to instance.)
(可以利用 2、3 中的步驟實現對接受消息對象的轉移,可以實現“多重繼承”的效果。)