Objective-C 中的 Method Swizzling 是一種可以在程序運行時,修改方法調用的技術。是 OC 作爲動態語言的典型證明。
Method Swizzling 是 OC <objc/runtime.h>
類庫提供的“黑魔法”之一。
例子
以替換 NSArray 的 lastObject 方法爲例:
- 在 NSArray 中添加需要替換 lastObject 的方法 – xxx_lastObject方法:
#import "NSArray+Swizzle.h"
@implementation NSArray (Swizzle)
- (id)xxx_lastObject
{
id ret = [self xxx_lastObject];
NSLog(@"********** myLastObject ***********");
return ret;
}
@end
注意這裏的寫法,xxx_lastObject 方法的方法體中調用了 [self xxx_lastObject],這樣寫並不會造成遞歸,後面會交換 xxx_lastObject 與 lastObject 的 IMP,其實 [self xxx_lastObject] 將會執行 [self lastObject] 。
- 調換 IMP
#import <objc/runtime.h>
#import "NSArray+Swizzle.h"
Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject));
Method my_Method = class_getInstanceMethod([NSArray class], @selector(xxx_lastObject));
method_exchangeImplementations(ori_Method, my_Method);
注意這裏需要引入 <objc/runtime.h>
,否則會報錯,因爲使用了
runtime 中的 method_exchangeImplementations 方法。
使用 method_exchangeImplementations
交換了
xxx_lastObject 與 lastObject 的 IMP。後面會講解更深層次的原理。
- 嘗試調用 lastObject
#import <objc/runtime.h>
#import "NSArray+Swizzle.h"
NSArray *array = @[@"0",@"1",@"2",@"3"];
NSString *string = [array lastObject];
NSLog(@"TEST RESULT : %@",string);
輸出結果爲:
1 2 |
|
很明顯,這裏調用 lastObject 方法,其實是調用了我們添加的 xxx_lastObject 方法。
用處
Method Swizzling 非常強大,主要作用有:
- 在不修改 iOS 系統類庫或第三方類庫的源碼基礎上,修改原有調用邏輯
- 動態添加、修改方法,修復線上 bug(如果 Apple 官方允許的話)
常用 API
相關常用方法,都在<objc/runtime.h>
包內:
//向類中添加Method
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//修改類的Method
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//交換2個方法中的IMP
void method_exchangeImplementations(Method m1, Method m2)
//獲取類的某個實例方法
Method class_getInstanceMethod(Class aClass, SEL aSelector);
底層原理
在運行時,OC 的方法是一種叫 Method
的結構體,這種 objc_method
類型的結構體定義爲:
struct objc_method
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
- method_name 是方法的 selector,可以理解爲運行時的方法名;
- *method_types 是一個參數和返回值類型編碼的字符串;
- method_imp 是指向方法實現的指針。
Method
Swizzling 的實質是在運行時,訪問對象的方法結構體,並改變它的底層實現。
我們以上節修改 NSArray 的 lastObject 爲例做說明。
- 初始時,lastObject 與 xxx_lastObject 方法的 Method 結構體:
-
Method lastObject { SEL method_name = @selector(lastObject) char *method_types = “v@:“ //返回值void, 參數id(self),selector(_cmd) IMP method_imp = 0x000FFFF //指向([NSArray lastObject]) } Method xxx_lastObject { SEL method_name = @selector(swizzle_originalMethodName) char *method_types = “v@:” IMP method_imp = 0x1234AABA //指向([NSArray xxx_lastObject]) }
-
調用
void method_exchangeImplementations(Method m1, Method m2)
,交換兩者的實現:
-
Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject)); Method my_Method = class_getInstanceMethod([NSArray class], @selector(xxx_lastObject)); method_exchangeImplementations(ori_Method, my_Method);
- 調換後的 Method 結構體:
Method lastObject {
SEL method_name = @selector(lastObject)
char *method_types = “@@:“ //返回值id, 參數id(self),selector(_cmd)
IMP method_imp = 0x1234AABA //指向([NSArray xxx_lastObject])
}
Method xxx_lastObject {
SEL method_name = @selector(swizzle_originalMethodName)
char *method_types = “@@:”
IMP method_imp = 0x000FFFF //指向([NSArray lastObject])
}
可以看到使用void
method_exchangeImplementations(Method m1, Method m2)
的實質是交換了xxx_lastObject 與 lastObject 的 IMP,實現了在運行時做方法的替換。使得當執行 [array lastObject] 的時候,實際會去執行 [array xxx_lastObject] 的方法實現。
其他提示
+load
Swizzling 的處理,在類的 +load
方法中完成。
因爲 +load
方法會在類被添加到
OC 運行時執行,且只會被調用一次,保證了 Swizzling 方法的及時處理。
dispatch_once
Swizzling 的處理,dispatch_once
中完成。保證只執行一次。
prefix
Swizzling 方法添加前綴,避免方法名稱衝突。
代碼示例
參考資料
How to swizzle a class method on iOS?
iOS 4.3: imp_implementationWithBlock()