運行時消息傳遞與轉發機制
前言
Objective-C是一門非常動態的語言,以至於確定調用哪個方法被推遲到了運行時,而非編譯時。與之相反,C語言使用靜態綁定,也就是說,在編譯期就能決定程序運行時所應該調用的函數,所以在C語言中,如果某個函數沒有實現,編譯時是不能通過的。而Objective-C是相對動態的語言,運行時還可以向類中動態添加方法,所以編譯時並不能確定方法到底有沒有對應的實現,編譯器在編譯期間也就不能報錯。
本文將簡單介紹消息傳遞機制和消息轉發機制。這需要我們首先了解類對象以及方法列表這個概念,不清楚的同學可以參考筆者之前的文章類的本質-類對象,希望對你有所幫助。本篇文章如有不實之處,還請指正!
(一)對象的消息傳遞機制 objc_msgSend()
在對象上調用方法在Objective-C中非常普遍。用Objective-C的術語來講,這叫做「給某個對象發送某條消息」。消息有「名稱」或「選擇子(selector)」之說。消息可以接受參數,而且還可以有返回值。
在Objective-C中給對象發送消息是如下的格式:
id returnValue = [someObject messgeName:parameter];
本例中,someObject叫做方法調用者,也叫做接受者(receiver)。messageName:是方法名,也叫做選擇子(selector)。選擇子與參數合起來叫做」消息「(message)。在運行時,編譯器會把上面這個格式的方法調用轉化為一條標準的C語言函數調用,該函數就是鼎鼎有名的objc_msgSend(),該函數是消息objc里在運行時傳遞機制中的核心函數,其原型如下:
void objc_msgSend(id self, SEL cmd, ...)
這是一個參數個數可變的函數。能接收兩個或兩個以上的參數,第一個參數代表接受者,第二個參數代表選擇子。後續參數就是消息中的那些參數,其順序不變。接受者就是調用方法的對象或者類(本質上類也是對象,叫做類對象)。選擇子就是指的方法的名稱,選擇子和方法這兩個詞經常交替使用,其實是指的一個意思。運行時,上面Objc的方法調用會被翻譯成一條C語言的函數調用,如下:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter)
消息傳遞流程
objc_msgSend()函數會一句接受者(調用方法的對象)的類型和選擇子(方法名)來調用適當的方法。
接收者會根據isa指針找到接收者自己所屬的類,然後在所屬類的」方法列表「(method list)中從上向下遍歷。如果能找到與選擇子名稱相符的方法,就根據IMP指針跳轉到方法的實現代碼,調用這個方法的實現。
如果找不到與選擇子名稱相符的方法,接收者會根據所屬類的superClass指針,沿著類的繼承體系繼續向上查找(向父類查找),如果 能找到與名稱相符的方法,就根據IMP指針跳轉到方法的實現代碼,調用這個方法的實現。
如果在繼承體系中還是找不到與選擇子相符的方法,此時就會執行」消息轉發(message forwarding)「操作。
(二)消息轉發流程
上面講到了對象的消息傳遞機制,如果在整個類的繼承體系中還是找不到與選擇子相符的方法,也就是對象或者類對象收到了無法解讀的消息,那麼就會進入到消息轉發環節。
在編譯期,向對象或者類對象發送了其無法解讀的消息並不會報錯,因為在運行期可以繼續向類和元類(metaClass)中添加方法,所以編譯器在編譯期還無法確定類中到底會不會有某個方法的實現。當對象接收到無法解讀的消息後,就會啟動「消息轉發(message forwarding)」機制,我們可以在消息轉發過程中告訴對象應該如處理未知消息。
消息轉發分為兩個階段。第一階段叫做「動態方法解析(dynamic method resolution)」,或者叫「動態方法決議」。第二階段涉及到「完整的消息轉發機制(full forwarding mechanism)」,或者叫「完整的消息轉發原理」。
(2.1)動態方法解析
動態方法解析的意思就是,徵詢消息接受者所屬的類,看其是否能動態添加方法,以處理當前「這個未知的選擇子(unknown selector)「。實例對象在接受到無法解讀的消息後,首先會調用其所屬類的下列類方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
類對象在接受到無法解讀的消息後,那麼運行期系統就會調用另外的一個方法,如下:
+ (BOOL)resolveClassMethod:(SEL)selector
如果運行期系統已經執行完了動態方法解析,那麼消息接受者自己就無法再以動態新增方法的形式來響應包含該未知選擇子的消息了,此時就進入了第二階段——完整的消息轉發。運行期系統會請求消息接受者以其他手段來處理與消息相關的方法調用。
(2.2)完整的消息轉發
完整的消息轉發又分為兩個階段,第一階段稱為備援接受者(replacement receiver),第二階段才是啟動完整的消息轉發機制。
(2.2.1)備援接收者(replacement receiver)
當前接受者如果不能處理這條消息,運行期系統會請求當前接受者讓其他接受者處理這條消息,與之對應的方法是:
- (id)forwardingTargetForSelector:(SEL)selector
方法參數代表未知的選擇子,返回值為備援接受者,若當前接受者能找到備援接受者,就直接返回,這個未知的選擇子將會交由備援接受者處理。如果找不到備援接受者,就返回nil,此時就會啟用」完整的消息轉發機制「。
(2.2.2)完整的消息轉發
如果轉發演算法已經來到了這一步,那麼代表之前的所有轉發嘗試都失敗了,此時只能啟用完整的消息轉發機制。完整的消息轉發機制是這樣的:首先創建NSInvocation對象,把尚未處理的那條消息有關的全部細節封裝於這個NSInvocation對象中。此對象中包含選擇子(selector)、目標(target)及參數。在觸發NSInvocation對象時,」消息派發系統(message-dispatch system)「將親自觸發,把消息派發給目標對象。此步驟中會調用下面這個方法來轉發消息:
- (void)forwardInvocation:(NSInvocation *)invocation
消息派發系統觸發消息前,會以某種方式改變消息內容,包括 但不限於額外追加一個參數、改變選擇子等。
實現此方法時,如果發現調用操作不應該由本類處理,則需要沿著繼承體系,調用父類的同名方法,這樣一來,繼承體系中的每個類都有機會處理這個調用請求,直至rootClass,也就是NSObject類。如果最後調用了NSObject的類方法,那麼該方法還會繼而調用」doesNotRecognizeSelector:「以拋出異常,此異常表明選擇子最終也未能得到處理。消息轉發到此結束。
關於does NotRecognizeSelector:你可能感到陌生,但是對於類似於unrecognized selector send to instance xxx這樣的錯誤,你可能並不陌生。這種錯誤通常是因為調用了某個對象或者某個類里不存在的方法,從而觸發了消息轉發機制,最終把這個未識別的消息發送給了NSObject的默認實現。
ps:方法緩存
通篇下來,發現調用一個方法並不像我們想的那麼簡單,更不像我們寫的那麼簡單,一個方法的執行其實底層需要很多步驟。正因如此,objc_msgSend()會將調用過且匹配到的方法緩存在」快速映射表(fast map)「中,快速映射表就是方法的緩存表。每個類都有這樣一個緩存。所以,即便子類實例從父類的方法列表中取過了某個對象方法,那麼子類的方法緩存表中也會緩存父類的這個方法,下次調用這個方法,會優先去當前類(對象所屬的類)的方法緩存表中查找這個方法,這樣的好處是顯而易見的,減少了漫長的方法查找過程,使得方法的調用更快。同樣,如果父類實例對象調用了同樣的方法,也會在父類的方法緩存表中緩存這個方法。
同理,如果用一個子類對象調用某個類方法,也會在子類的metaclass里緩存一份。而當用一個父類對象去調用那個類方法的時候,也會在父類的metaclass里緩存一份。
參考文
《Effetive Objective-C 2.0 編寫高質量iOS與OS X代碼的52個有效方法》第12條:理解消息轉發機制
文/VV木公子(簡書作者)
如果您是iOS開發者,或者對本篇文章感興趣,請關注本人,後續會更新更多!敬請期待!
※AIOps是什麼?它與AI有什麼關係?
※訪談Michael Ong:關於騎行、敏捷和UX價值
※淺談《守望先鋒》中的 ECS 構架
※FFmpeg曝任意文件讀取漏洞
※2017年上半年,全球計算機硬體與存儲市場研究報告
TAG:推酷 |
※運20產量被俄發動機限制之際,官方不經意巨泄了一個大消息
※物聯網最新動態與即時消息
※為何突然高調公布艦載預警機消息?國產發動機竟一步彎道超車
※消息稱谷歌正在打造新一代基於串流服務的遊戲機
※阿里巴巴回應發行 CDR 進程推遲消息:態度從未改變
※等待消息發送中
※好消息!轉行政編製,消防員將會成為一個香饃饃
※消息稱蘋果將與大眾合作打造自動駕駛員工接駁班車
※屏蔽垃圾消息、流暢運行遊戲 想上王者很簡單
※不幸消息!又一架俄制戰機發生墜機,飛行員不幸遇難!
※銀行大消息!央行放開商業銀行存款利率自律上限
※研究證實假消息傳播得更快更廣
※貴州傳來好消息:航空發動機又有新突破,助力二代艦載機研發
※為何如此高調宣布國產大飛機消息:發動機關鍵技術已開始彎道超車
※殲-20又傳出一個重大好消息,疑似配備國產太行發動機進行試飛
※為何突然高調公布中轟消息?國產發動機居然一步彎道超車
※《精英危機》發布新章節,音頻消息流讓玩家實時跟上故事情節
※銀行代扣通道6月末全部關閉、金立手機工廠開始遣散員工、魅族將進行千人規模大裁員、抖音已支持發布圖文消息
※我空軍發布軍機繞台消息 台媒不得不贊:創下主動宣布的紀錄!
※不用流量就能看新聞、轉賬、發消息?這家公司說可以