文章目錄
- 方法交換
- 什么是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
,參數為id
和SEL
)。 -
返回值:
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
注意事項
- 線程安全:在
+load
中使用dispatch_once
確保 Swizzling 僅執行一次。 - 避免循環調用:在交換后的方法中調用原方法時,必須通過交換后的選擇器(如
self.swizzled_viewWillAppear:
)。 - 方法簽名匹配:確保交換的方法參數和返回值類型一致,否則可能導致崩潰。
- 影響范圍:Swizzling 會影響類及其子類的所有實例,需謹慎操作。
使用方法交換注意事項
線程安全
??mehod-swizzling
方法交換一般寫在load
方法中,而load
方法會主動調用多次
,這樣會導致方法的重復交換
,使方法sel的指向又恢復成原來的imp。所以我們需要通過單例模式,使方法交換只執行一次,我們可以通過dispatch_once
來實現單例。
方法交換的影響范圍
??mehod-swizzling
方法交換會影響類及其子類的所有實例。