Runtime-方法調用以及消息轉發

objc_object, objc_class 以及 Ojbc_method

在 Objective-C 中,每個類都有一個isa指針,在isa結構體中有一個objc_method_list(該類的方法列表),每個方法是objc_method。一個 objc_method 結構體中包含函數名,也就是SEL,有表示函數類型的字符串 (見 Type Encoding) 、方法參數類型以及函數的實現IMP。objc_method結構體如下:

struct objc_method {

? ? SEL _Nonnull method_name ? ? ? ? ?//方法名稱 ? ? ? ? ??

? ? char * _Nullable method_types ? ? ?//方法參數類型 ? ? ? ? ? ? ? ? ? ??

? ? IMP _Nonnull method_imp ? ? ? ? ? ? //方法實現地址

} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

先講一下我理解的消息發送(調用方法),在OC中調用一個方法,本質上是給一個對象發送了一條消息(類方法也一樣,類是元類的對象)。比如[obj foo]調用obj的foo方法相當于objc_msgSend(obj, foo) ,即給obj發送foo消息。

方法調用的實現步驟如下(對象方法):

1.從isa結構體的objc_cache中查找是否緩存了該方法,如果緩存了則去方法實現地址實現該方法,結束。

2.若緩存中沒有,則從isa的objc_method_list中查找該方法,找到,則跳到方法的實現實現該方法,然后把該方法的method_name作為key,method_imp作為value存入objc_cache中,結束。

3.若在該isa中沒有找到方法,則通過isa的super_class找到父類,在他父類中重復第一步和第二步,若還沒有找到,則繼續往父類查找。

objc_cache存在的好處:

一個 class 往往只有 20% 的函數會被經常調用,可能占總調用次數的 80% 。每個消息都需要遍歷一次 objc_method_list 并不合理。如果把經常被調用的函數緩存下來,那可以大大提高函數查詢的效率。這就是objc_cache的作用。 - 再找到 foo 之后,把 foo 的 method_name 作為 key ,method_imp 作為 value 給存起來。當再次收到 foo 消息的時候,可以直接在 cache 里找到,避免去遍歷 objc_method_list。

動態方法解析和轉發

調用方法后如果在運行時經過了上述的步驟還是沒有找到該方法(調用了沒有實現的方法),一般情況程序會在運行時掛掉并拋出 unrecognized selector sent to … 的異常。但在異常拋出前,Objective-C 的運行時會給你三次拯救程序的機會:

1.Method Resolution(動態方法解析)

Objective-C 運行時會調用 +resolveInstanceMethod: 或者 +resolveClassMethod:,可以提供一個函數實現。如果你添加了函數并返回 YES(如果在返回NO前,給class添加了該方法的實現,則仍然會調用該方法), 那運行時系統就會重新啟動一次消息發送的過程。

如:創建一個類Test,聲明一個對象方法和一個類方法,并不實現他們

運行時,在遇到沒有實現的方法時,首先會調用所屬類(Test)的類方法+resolveInstanceMethod:(對象方法)或者+resolveClassMethod:(類方法)。在這個方法中,可以為該未知消息新增一個“處理方法”。

使用步驟:

1.聲明一個函數實現,即下面的void functionForMethod(id obj, SEL _cmd){}。

2.使用class_addMethod函數把該實現動態添加到類里面。此時就會調用聲明的函數,而不會奔潰。


如果 resolve 方法返回 NO,或者未實現resolve方法(默認是沒有其他函數可以接受該消息的),運行時就會移到下一步:

2.Message Forwarding(消息轉發)

動態方法解析無法處理消息的時候,會走消息轉發。如果目標對象(Test)實現了 -forwardingTargetForSelector: ,Runtime 這時就會調用這個方法,把這個消息轉發給其他對象去實現。(測試只能轉發對象方法)。

消息轉發的接收者不能是自己,需要新建一個類來接收轉發的消息。

新建RuntimeMethodHelper類,實現需要接收的轉發消息

在Test中實現-forwardingTargetForSelector:方法,此時會調用RuntimeMethodHelper中的hello方法

如果不實現該方法或者返回的是nil或者self,則會走創建一個 NSInvocation 對象的消息轉發。

3.完整消息轉發(創建NSInvocation)

這一步是 Runtime 最后一次挽救的機會。

首先它會發送 -methodSignatureForSelector: 消息獲得函數的參數和返回值類型。

如果 -methodSignatureForSelector: 返回 nil ,Runtime 則會發出 -doesNotRecognizeSelector: 消息,程序這時也就掛掉了。

如果返回了一個函數簽名,Runtime 就會創建一個 NSInvocation 對象并發送 -forwardInvocation: 消息給當前對象。

NSInvocation 實際上就是對一個消息的描述,包括selector 以及參數等信息。所以你可以在 -forwardInvocation: 里修改傳進來的 NSInvocation 對象,然后發送 -invokeWithTarget: 消息,傳進去一個新的目標(響應該方法的對象):


Cocoa 里很多地方都利用到了消息傳遞機制來對語言進行擴展,如 Proxies、NSUndoManager 跟 Responder Chain。NSProxy 就是專門用來作為代理轉發消息的;NSUndoManager 截取一個消息之后再發送;而 Responder Chain 保證一個消息轉發給合適的響應者。

總結

Objective-C 中給一個對象發送消息會經過以下幾個步驟:

在對象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應的函數IMP去執行實現代碼;

如果沒有找到,Runtime 會發送 +resolveInstanceMethod: 或者 +resolveClassMethod: 嘗試去 resolve 這個消息;

如果 resolve 方法返回 NO,Runtime 就發送 -forwardingTargetForSelector: 允許你把這個消息轉發給另一個對象;

如果沒有新的目標對象返回, Runtime 就會發送 -methodSignatureForSelector:和 -forwardInvocation: 消息。你可以發送 -invokeWithTarget: 消息來手動轉發消息或者發送 -doesNotRecognizeSelector: 拋出異常。

參考:Objective-C Runtime

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374