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

来源:https://www.cnblogs.com/simplepp/archive/2020/05/16/12900869.html
-Advertisement-
Play Games

本文章將記錄Objective-C中消息傳遞和轉發機制、Method Swizzling的相關資料,如有錯誤歡迎指出~ Objective-C 本質上是一種基於 C 語言的領域特定語言。C 語言是一門靜態語言,其在編譯時決定調用哪個函數。而 Objective-C 則是一門動態語言,其在編譯時不能決 ...


本文章將記錄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 通過以下幾個步驟實現了動態綁定機制。

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

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

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

當創建一個新對象時,先為其分配記憶體,並初始化其成員變數。其中 isa 指針也會被初始化,讓對象可以訪問類及類的繼承鏈。

下圖所示為消息傳遞過程的示意圖。

 

 
  • 當消息傳遞給一個對象時,首先從運行時系統緩存 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 的映射關係。如下圖所示:

 

推薦

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 好久沒更新博客了,今年整體行業不太樂觀,在朋友的引薦下進了新的東家討口飯吃,難得清靜下來一個周末,好吧,廢話不多說了, 今天更新了windows 的docker客戶端docker-toolbox, 發現原來的docker login -u 用戶名 -p 密碼 使用不了。這次更新的應該是最新版:htt ...
  • 一.安裝部署 "1.Zabbix部署" "2.Nessus簡介與安裝" "3.Ceph安裝" "4.Graylog 安裝" "5.Centos6.10 安裝Python 2.7.16" 更新中... 二.Linux運維 更新中... ...
  • BIOS 中英文對照表 轉載於https://www.dell.com/support/article/zh-cn/sln262122/bios-%E4%B8%AD%E8%8B%B1%E6%96%87%E5%AF%B9%E7%85%A7%E8%A1%A8?lang=zh 註意:機型不同 BIOS 版 ...
  • Redis 系列: 1. "Redis系列(一)Redis入門" 2. "Redis系列(二)Redis的8種數據類型" 3. "Redis系列(三)Redis的事務和Spring Boot整合" 4. "Redis系列(四)Redis配置文件和持久化" 5. "Redis系列(五)發佈訂閱模式、主 ...
  • 假設有一個用戶表 user,數據如下: 1、查詢表中 uid 重覆的數據 SELECT id, uid, name FROM USER WHERE uid IN (SELECT uid FROM USER GROUP BY uid HAVING COUNT(uid) > 1); 2、查詢表中重覆數據 ...
  • 在SQL Server 2017的錯誤日誌中出現"Parallel redo is started for database 'xxx' with worker pool size [2]"和“Parallel redo is shutdown for database 'xxx' with wor... ...
  • Redis 伺服器將所有的資料庫都保存在伺服器狀態redisServer結構的db數組中,db數組的每個項都是一個redisDB: struct redisServer{ //一個數組保存著伺服器中的所有資料庫 redisDb *db; //資料庫的個數 int dbnum; } dbnum:伺服器 ...
  • 一、使用碎片來進行一個最佳實踐,即我們寫一個新聞的app 1.首先先建立一個新聞類 package com.example.fragmentbestpractice; ​ public class News { private String title; private String content ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...