【iOS面試糧食】Runtime—消息傳遞和轉發機制、Method Swizzling

本文章將記錄Objective-C中消息傳遞和轉發機制、Method Swizzling的相關資料,若有錯誤歡迎指出~面試

Objective-C 本質上是一種基於 C 語言的領域特定語言。C 語言是一門靜態語言,其在編譯時決定調用哪一個函數。而 Objective-C 則是一門動態語言,其在編譯時不能決定最終執行時調用哪一個函數(Objective-C 中函數調用稱爲消息傳遞)。Objective-C 的這種動態綁定機制正是經過 runtime 這樣一箇中間層實現的。緩存

消息傳遞(方法調用)

在 Objective-C 中,消息直到運行時才綁定到方法實現上。編譯器會將消息表達式轉化爲一個消息函數的調用。數據結構

OC中的消息表達式以下(方法調用)架構

id returnValue = [someObject messageName:parameter];

編譯器看到這條消息會轉換成一條標準的 C 語言函數調用函數

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);

咱們能夠看到轉換中,使用到了objc_msgSend 函數,這個函數將消息接收者和方法名做爲主要參數,以下所示:工具

objc_msgSend(receiver, selector)                    // 不帶參數
objc_msgSend(receiver, selector, arg1, arg2,...)    // 帶參數

objc_msgSend 經過如下幾個步驟實現了動態綁定機制。post

  • 首先,獲取 selector 指向的方法實現。因爲相同的方法可能在不一樣的類中有着不一樣的實現,所以根據 receiver 所屬的類進行判斷。
  • 其次,傳遞 receiver 對象、方法指定的參數來調用方法實現。
  • 最後,返回方法實現的返回值。

消息傳遞的關鍵在於【iOS面試糧食】Runtime—實例對象、類對象、元類對象記錄過的 objc_class 結構體,其有兩個關鍵的字段:學習

  • isa:指向父類的指針
  • methodLists: 類的方法分發表(dispatch table

當建立一個新對象時,先爲其分配內存,並初始化其成員變量。其中 isa 指針也會被初始化,讓對象能夠訪問類及類的繼承鏈。spa

下圖所示爲消息傳遞過程的示意圖。架構設計

 

 
  • 當消息傳遞給一個對象時,首先從運行時系統緩存 objc_cache 中進行查找。若是找到,則執行。不然,繼續執行下面步驟。
  • objc_msgSend 經過對象的 isa 指針獲取到類的結構體,而後在方法分發表 methodLists 中查找方法的 selector。若是未找到,將沿着類的 isa 找到其父類,並在父類的分發表 methodLists 中繼續查找。
  • 以此類推,一直沿着類的繼承鏈追溯至 NSObject 類。一旦找到 selector,傳入相應的參數來執行方法的具體實現,並將該方法加入緩存 objc_cache 。若是最後仍然沒有找到 selector,則會進入消息轉發流程

做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人點擊加入羣聊iOS交流羣:789143298 ,無論你是小白仍是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!

 

消息轉發

當一個對象能接收一個消息時,會走正常的消息傳遞流程。當一個對象沒法接收某一消息時,會發生什麼呢?

  • 默認狀況下,若是以 [object message] 的形式調用方法,若是 object 沒法響應 message 消息時,編譯器會報錯。
  • 若是是以 performSeletor: 的形式調用方法,則須要等到運行時才能肯定 object 是否能接收 message 消息。若是不能,則程序崩潰。

對於後者,當不肯定一個對象是否能接收某個消息時,能夠調用 respondsToSelector: 來進行判斷。

if ([self respondsToSelector:@selector(method)]) {
    [self performSelector:@selector(method)];
}

事實上,當一個對象沒法接收某一消息時,就會啓動所謂「消息轉發(message forwarding)」機制。經過消息轉發機制,咱們能夠告訴對象如何處理未知的消息。

消息轉發機制大體可分爲三個步驟:

  • 動態方法解析(Dynamic Method Resolution)
  • 備用接收者
  • 完整消息轉發

下圖所示爲消息轉發過程的示意圖。

 

動態方法解析

這是整個消息轉發流程的第一個階段,若是在收到沒法響應的消息後,會調用所屬類的方法:

//實例對象
+ (BOOL)resolveInstanceMethod:(SEL)selector
//類對象
+ (BOOL)resolveClassMethod:(SEL)selector

其中參數selector爲未處理的方法。

返回值@return表示可否新增一個方法來處理,通常使用@dynamic屬性來實現:

/************** 使用 resolveInstanceMethod 實現 @dynamic 屬性 **************/
id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);
+ (BOOL)resolveInstanceMethod:(SEL)selector
{
    NSString *selectorString = NSStringFromSelector(selector);
    if (/* selector is from a @dynamic property */)
    {
        if ([selectorString hasPrefix:@"set"])
        {
            // 添加 setter 方法
            class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
        }
        else
        {
            // 添加 getter 方法
            class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
    }
    return [super resolveInstanceMethod:selector];
}

備援接受者

這是整個消息轉發機制的第二站,看名字就能夠看出來,這是在尋找一個備用援救的接受者,到了這一階段,系統會調用這個方法:

- (id)forwardingTargetForSelector:(SEL)aSelector;

傳入參數aSelector一樣爲沒法處理的方法。

返回值爲當前找到的備援接受者,若是沒有則返回nil,進入下一階段。

完整的消息轉發機制

若是前兩個階段都沒有辦法處理消息,就會啓動完整的消息轉發機制。

首先會建立NSInvocation對象,把還沒有處理的那條消息的所有信息細節裝在裏邊,在觸發NSInvocation對象時,系統派發系統(message-dispatch system)將會把消息指派給目標對象。這時會調用該方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation;

傳入的參數anInvocation就包含了消息的全部內容。

若是此時仍是沒辦法處理消息,就會沿着繼承的順序一步一步向父類調用相同的方法,直到最後的NSObject類中,這時候若是尚未辦法處理消息,就會調用doesNotRecognizeSelector:拋出異常。

到此爲止,消息轉發的整個流程就都結束了。

Method Swizzling

談到黑科技,就不得不提一下Objective-C 中的 Method Swizzling 技術,它能夠容許咱們動態地替換方法的實現,實現 Hook 功能,是一種比子類化更加靈活的「重寫」方法的方式。就是說在開發中,咱們可能會遇到系統提供的 API 不能知足實際需求,咱們但願可以修改它以達到指望的效果。

Method Swizzling 原理

Method Swizzling 的實現充分利用了動態綁定機制。

在 Objective-C 中調用方法,實際上是向一個對象發送消息,而查找消息的惟一依據是方法名 selector。每一個類都有一個方法列表 objc_method_list,存放着其全部的方法 objc_method

typedef struct objc_method *Method
struct objc_method{
    SEL method_name      OBJC2_UNAVAILABLE; // 方法名
    char *method_types   OBJC2_UNAVAILABLE;
    IMP method_imp       OBJC2_UNAVAILABLE; // 方法實現
}

每一個方法 objc_method 保存了方法名(SEL)和方法實現(IMP)的映射關係。Method Swizzling 其實就是重置了 SEL 和 IMP 的映射關係。以下圖所示:

 

推薦👇:

  • 020 持續更新,精品小圈子每日都有新內容,乾貨濃度極高。

  • 結實人脈、討論技術 你想要的這裏都有!

  • 搶先入羣,跑贏同齡人!(入羣無需任何費用)

  • (直接搜索羣號:789143298,快速入羣)
  • 點擊此處,與iOS開發大牛一塊兒交流學習

申請即送:

  • BAT大廠面試題、獨家面試工具包,

  • 資料免費領取,包括 數據結構、底層進階、圖形視覺、音視頻、架構設計、逆向安防、RxSwift、flutter,

     
相關文章
相關標籤/搜索