【iOS】源碼閱讀(六)——方法交換

文章目錄

  • 方法交換
    • 什么是Method-Swizzling
    • 方法交換核心API
        • **1. 獲取方法對象**
        • **2. 添加/替換方法實現**
        • **3. 交換方法實現**
        • **4. 獲取方法信息**
        • **5. 修改方法實現**
      • **使用示例:完整的 Method-Swizzling 流程**
      • **注意事項**
    • 使用方法交換注意事項
      • 線程安全
      • 方法交換的影響范圍

方法交換

什么是Method-Swizzling

??Method-Swizzling 翻譯過來就是方法交換,它是 Objective-C 運行時(Runtime)提供的一種動態修改方法實現的機制。它的核心是通過交換兩個方法的 IMP(方法實現)來實現功能的動態注入或修改,常用于 AOP(面向切面編程)、調試、日志統計等場景。

關于runtime

runtime是oc的核心機制,oc語言編程時,許多行為(如方法調用、類結構)在程序運行時才確定。

具體表現在其編程時的動態類型(對象的類型在運行時確定)、動態綁定(方法調用在運行時解析,而非編譯時)、動態加載(按需加載類和資源)、消息傳遞機制(方法調用本質是向對象發送消息(objc_msgSend))。

關于IMP

IMP是 Objective-C 方法的底層實現,本質上是一個指向 C 函數的指針,格式為 id (*)(id, SEL, ...)

每個方法(Method)對應一個 IMP,它決定了方法被調用時的具體行為。

??在之前的學習中,我們可以知道,OC的方法調用通過 objc_msgSend 函數發送消息,運行時根據 SEL(方法名)查找對應的 IMP 并執行。

關于AOP與OOP

AOP主要解決的是橫切關注點的問題,也就是那些在多個模塊中重復出現的功能,比如日志、安全、事務管理等。這些功能如果分散在各個類中,會導致代碼冗余和耦合度高。AOP通過切面(Aspect)將這些橫切關注點模塊化,從而在編譯期或運行時將它們織入到目標代碼中。

OOP的核心是類、對象、繼承、封裝和多態。它通過將數據和行為封裝在對象中,利用繼承和多態來實現代碼的復用和擴展。

這樣說可能會比較抽象,我們來舉個具體的例子。

假設我們現在需要實現一個電商系統的訂單模塊,那么其中:

OOP的職責就類似于定義 Order 類,封裝訂單屬性(訂單號、金額、狀態);實現 OrderService 類,處理訂單創建、支付、取消等業務邏輯等。

AOP 的職責就類似于通過切面 LoggingAspect,自動記錄訂單操作的日志。通過切面 TransactionAspect,為支付方法添加事務管理。通過切面 PermissionAspect,校驗用戶是否有權限取消訂單等。

了解完以上概念之后,我們再回來繼續認識Method-Swizzling。

說白了,Method-Swizzling就是在程序運行時將一個方法的實現替換成另一個方法的實現,在OC中,每個類都維護著一個方法列表,即methodList,methodList中有不同的方法,即method,每個方法包含了方法的sel和IMP,方法交換就是將sel和IMP原本的對應斷開,并將sel和新的IMP生成對應關系。如下圖:

請添加圖片描述

方法交換核心API

OC中的Method-Swizzling 依賴于 Runtime 提供的 API,主要用于操作類和方法的實現(IMP)。

因此,我們在使用方法交換相關API之前,要先導入Runtime相關的頭文件:

#import "objc/runtime.h"
1. 獲取方法對象

通過選擇器(SEL)和類獲取 Method 結構體,包含方法的實現(IMP)和類型編碼(Type Encoding)。

class_getInstanceMethod

Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewWillAppear:));

這是一個用于獲取類中實例方法的函數,其中第一個參數是獲取 UIViewController 類的類對象(Class Object);第二個參數是定義一個方法選擇器(SEL),指向 viewWillAppear: 方法。這個函數最終會返回一個Method類型的結構體指針。

這里我們可以找到Method的源碼:

typedef struct objc_method *Method;/// Defines a method
struct objc_method_description {SEL _Nullable name; /**< The name of the method *///方法的唯一標識符(通過 @selector(methodName) 生成)。char * _Nullable types; /**< The types of the method arguments *///方法的類型編碼字符串(如 "v@:@" 表示返回 void,參數為 id 和 SEL)。
};

class_getClassMethod

Method classMethod = class_getClassMethod([NSArray class], @selector(arrayWithObject:));

這是一個用于獲取類方法(Class Method)的函數,參數與返回類型同上。

2. 添加/替換方法實現

用于動態修改類的方法列表。

class_addMethod

BOOL success = class_addMethod([self class], @selector(newMethod), (IMP)newMethodIMP, "v@:");

這個函數用于向類中添加新的方法實現(該方法原不存在)。

  • Class cls:目標類。

  • SEL name:方法選擇器。

  • IMP imp:方法實現(C 函數指針)。

  • const char *types:方法類型編碼(如 "v@:" 表示返回 void,參數為 idSEL)。

  • 返回值BOOL,表示是否成功添加。

class_replaceMethod

class_replaceMethod([UIViewController class], @selector(viewWillAppear:), (IMP)swizzled_viewWillAppear, "v@:");

若方法存在則替換類的方法實現,若不存在則進行添加。

  • 參數:同 class_addMethod
  • 返回值BOOL,表示是否成功替換或添加。
3. 交換方法實現

直接交換兩個方法的 IMP,是 Method-Swizzling 的核心操作。

method_exchangeImplementations

Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewWillAppear:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(swizzled_viewWillAppear:));
method_exchangeImplementations(originalMethod, swizzledMethod);

交換兩個 Method 對象的 IMP。

  • 參數Method m1, Method m2
  • 返回值void
4. 獲取方法信息

輔助函數,用于提取方法的元數據。

method_getName

SEL selector = method_getName(originalMethod);

獲取方法的 SEL。

  • 參數Method m
  • 返回值SEL

method_getTypeEncoding

const char *typeEncoding = method_getTypeEncoding(originalMethod);

獲取方法的類型編碼(如參數和返回值類型)。

  • 參數Method m
  • 返回值const char *

method_getImplementation

IMP originalIMP = method_getImplementation(originalMethod);

獲取方法的 IMP。

  • 參數Method m
  • 返回值IMP
5. 修改方法實現

直接設置方法的 IMP。

method_setImplementation

  • 作用:直接修改方法的 IMP。
  • 參數Method m, IMP imp
  • 返回值IMP(舊實現)。
IMP oldIMP = method_setImplementation(originalMethod, newIMP);

使用示例:完整的 Method-Swizzling 流程

有兩種編寫方法:一種是寫在需要實現方法交換的Category分類文件中;第二種就是寫在專門用于 Swizzling 的類中,創建一個 MethodSwizzlingHelper.m 文件,集中管理所有 Swizzling 邏輯。

#import <objc/runtime.h>@implementation UIViewController (Swizzle)+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Class class = [self class];SEL originalSelector = @selector(viewWillAppear:);SEL swizzledSelector = @selector(swizzled_viewWillAppear:);Method originalMethod = class_getInstanceMethod(class, originalSelector);Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);// 嘗試添加方法(避免父類已實現但當前類未實現)BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));if (didAddMethod) {// 添加成功,替換原方法class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));} else {// 直接交換實現method_exchangeImplementations(originalMethod, swizzledMethod);}});
}- (void)swizzled_viewWillAppear:(BOOL)animated {// 在原始方法前插入邏輯NSLog(@"View will appear: %@", NSStringFromClass([self class]));// 調用原始方法(實際執行交換后的 IMP)[self swizzled_viewWillAppear:animated];
}@end

注意事項

  1. 線程安全:在 +load 中使用 dispatch_once 確保 Swizzling 僅執行一次。
  2. 避免循環調用:在交換后的方法中調用原方法時,必須通過交換后的選擇器(如 self.swizzled_viewWillAppear:)。
  3. 方法簽名匹配:確保交換的方法參數和返回值類型一致,否則可能導致崩潰。
  4. 影響范圍:Swizzling 會影響類及其子類的所有實例,需謹慎操作。

使用方法交換注意事項

線程安全

??mehod-swizzling方法交換一般寫在load方法中,而load方法會主動調用多次,這樣會導致方法的重復交換,使方法sel的指向又恢復成原來的imp。所以我們需要通過單例模式,使方法交換只執行一次,我們可以通過dispatch_once來實現單例。

方法交換的影響范圍

??mehod-swizzling方法交換會影響類及其子類的所有實例。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92052.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92052.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92052.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mysql運維問題解決:MySQL主從延遲(鎖阻塞與讀寫分離)

小亦平臺會持續給大家科普一些運維過程中常見的問題解決案例&#xff0c;運維朋友們可以在常見問題及解決方案專欄查看更多案例 問題概述 告警事件&#xff1a; 2023-07-28 03:31:39.571 首次觸發主從延遲告警&#xff08;延遲1515秒&#xff09;2023-07-28 07:41:37 告警解除…

SSH 密鑰

什么是 SSH 密鑰 SSH 密鑰就像是你電腦的“身份證”和“鑰匙”&#xff0c; 用來安全登錄另一臺電腦&#xff08;服務器&#xff09;&#xff0c;而不需要每次輸入密碼。SSH 密鑰是一種安全登錄遠程服務器的方式&#xff0c;由一對加密的“鑰匙”組成&#xff1a;一個公鑰 一個…

st-Gcn訓練跳繩識別模型一:數據標注工具和標注流程

目錄 工具展示和使用說明 工具標注后文件展示說明 json轉換成單個npy文件 數據獲取補充 工具展示和使用說明 文件名labelV.py集于PySide6實現&#xff1a; 通過選擇視頻來選擇你要標注的視頻&#xff0c;然后選擇保存路徑&#xff1a; 然后視頻兩個類別。當你看見視頻中的人…

springboot跨域問題 和 401

springboot跨域問題 和 401 1.跨域import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotatio…

構建直播平臺大體的流程

? 直播流程完整鏈路&#xff08;基于 SRS OBS 前后端&#xff09;&#x1f9cd;?♂? 用戶操作流程&#xff1a;? 用戶登錄系統&#xff08;前端&#xff09;系統中校驗用戶身份&#xff08;JWT 等&#xff09;后端可能校驗權限&#xff0c;比如“是否有開播資格”? 用戶…

KOSMOS-2: 將多模態大型語言模型與世界對接

溫馨提示&#xff1a; 本篇文章已同步至"AI專題精講" KOSMOS-2: 將多模態大型語言模型與世界對接 摘要 我們介紹了 KOSMOS-2&#xff0c;一種多模態大型語言模型&#xff08;MLLM&#xff09;&#xff0c;賦予了模型感知物體描述&#xff08;例如&#xff0c;邊界框…

協作機器人操作與編程-PE系統示教編程和腳本講解(直播回放)

協作機器人操作與編程-PE系統示教編程和腳本講解本次講解主要圍繞協作機器人PE系統的操作與編程展開&#xff0c;內容涵蓋軟件安裝、虛擬機配置、手動操作、在線編程、變量設置、網絡通信及標定方法等方面。以下是主要內容要點提煉&#xff1a; 軟件安裝與虛擬機配置 需從官網下…

【前后端】Node.js 模塊大全

用到的全部總結在這里&#xff0c;不定期更新 鏈接 node一本通 包括&#xff1a; express path fs/ process/ os/ http/ mysql/mongoose/ express-jwt/jsonwebtoken/ dotenv/ multer/ swagger/ cors/ nodemon (docker篇有)常用模塊 內置 fs 文件系統操作&#xff08;讀寫、重命…

雙8無碳小車“cad【17張】三維圖+設計說名書

基于MATLAB的雙八無碳小車軌跡仿真及其結構設計 摘 要 本文設計的基于MATLAB的無碳小車來自于全國大學生工程訓練能力競賽&#xff0c;依據綠色環保&#xff0c;設計一種通過重力勢能轉換成動能來驅動小車行走的裝置。通過分析任務要求&#xff0c;本文完成了小車的三維結構設計…

視覺大模型離線部署全流程優化:從微調技術到工程實踐

視覺大模型離線部署全流程優化&#xff1a;從微調技術到工程實踐 一、視覺大模型離線部署概述 1.1 視覺大模型的應用場景與挑戰 視覺大模型在物體檢測、圖像生成、圖像描述等領域展現出強大能力&#xff0c;已成為人工智能領域的研究熱點和產業應用焦點(5)。隨著技術的發…

Vue中組件的生命周期

組件的生命周期生命周期、生命周期函數、生命周期鉤子vue2的生命周期創建&#xff08;創建前的生命周期函數 beforeCreate &#xff0c;創建完畢created&#xff09;掛載&#xff08;掛載前beforeMount&#xff0c;掛載完畢mounted&#xff09;//把組件放在頁面中更新&#xff…

securecrt連接服務器報錯 Key exchange failed 怎么辦

新買了一臺阿里云機&#xff0c;用securecrt去連接&#xff0c;如下報錯這個錯誤表明你的 SSH 客戶端與服務器之間無法就密鑰交換方法和主機密鑰算法達成一致&#xff0c;導致連接失敗。這通常是由于客戶端和服務器支持的加密算法集不匹配造成的。 解決方式 編輯服務器的/etc/s…

用協議分層模型實戰:從物理層到應用層的STM32協議棧開發

目錄 1. 揭開協議棧的神秘面紗:從STM32到分層思維 STM32的硬件優勢 本章實戰:點亮物理層的第一步 2. 數據鏈路層:讓STM32學會“打包”和“拆包” 以太網幀的那些事兒 實戰:解析以太網幀 3. 網絡層:讓STM32學會“找路” LwIP的快速上手 實戰:實現一個簡單的Ping …

微服務基礎環境搭建-centos7

文章目錄1、安裝docker1.1、安裝步驟1.2、docker常用命令2、安裝Nginx3、Docker安裝Mysql4、Docker安裝Redis5、安裝Nacos5.1、Nacos的作用5.2、單體服務安裝6、安裝RocketMQ服務6.1 MQ的作用6.2 RocketMQ的基礎服務架構6.2、安裝RocketMQ服務6.3、安裝dashboard面板服務6.4、R…

Netty知識點

一、Netty的零拷貝機制 零拷貝的基本理念&#xff1a;避免在用戶態和內核態之間拷貝數據&#xff0c;從而降低 CPU 占用和內存帶寬的消耗除了系統層面的零拷貝。 1、FileRegion 接口 FileRegion 是 Netty 提供的用于文件傳輸的接口&#xff0c;它通過調用操作系統的 sendfile 函…

Kafka的基本使用

目錄 認識Kafka 消息隊列 消息隊列的核心概念 核心價值與解決的問題 Kafka ZooKeeper Kafka的基本使用 環境安裝 啟動zookeeper 啟動Kafka 消息主題 創建主題 查詢主題 修改主題 發送數據 命令行操作 JavaAPI操作 消費數據 命令行操作 JavaAPI操作 認識Kafka…

Flink2.0學習筆記:Table API SQL

stevensu1/EC0720 表 API 和 SQL# 表 API 和 SQL——用于統一流和批處理 加工。表 API 是適用于 Java、Scala 和 Python 的語言集成查詢 API&#xff0c;它 允許組合來自關系運算符的查詢&#xff0c;例如 selection、filter 和 join in 一種非常直觀的方式。Flink 的 SQL 支…

【 SpringAI核心特性 | Prompt工程 】

1. Prompt 工程 基本概念&#xff1a;Prompt ?工程又叫提示?詞工程&#xff0c;簡單來說&#xff0c;就是輸入?給 AI 的指令。 比如下面?這段內容&#xff0c;就是提示詞&#xff1a; 請問桂林電子科技大學是一個怎么樣的學校&#xff1f;1.1 Prompt分類 在 AI ?對話中…

windows wsl2-06-docker hello world

hello-world 例子 就像其他任何一門語言一樣&#xff0c;我們來體驗 docker 的 hello world $ docker run hello-world但是報錯 :~$ docker run hello-world Unable to find image hello-world:latest locally docker: Error response from daemon: Get "https://registry…

Python知識點4-嵌套循環break和continue使用死循環

一、循環【重點掌握】 1.嵌套循環類似于嵌套if語句 語法&#xff1a; while 表達式1&#xff1a;while 表達式2&#xff1a;語句# 1. # 循環5次&#xff0c;打印0~4 m 0 while m < 5:print(m)m 1 # 循環3次&#xff0c;打印0~2 n 0 while n < 3:print(n)n 1print(&qu…