Method Swizzling(方法交換),顧名思義,就是將兩個方法的實現交換,即由原來的A-AImp、B-BImp對應關系變成了A-BImp、B-AImp。
那為什么無緣無故要將兩個方法的實現交換呢?
1、hook:在開發中,經常用到系統提供的API,但出于某些需求,我們可能會對某些方法的實現不太滿意,就想去修改它以達到更好的效果,Hook由此誕生。iOS開發會使用Method Swizzling來達到這樣的效果:當特定的消息發出時,會先到達我們提前預置的消息處理函數,取得控制權來加工消息以及后續處理。
2、面向切面編程:實際上,要改變一個方法的實現有幾種方法,比如繼承重寫、分類重寫等等,但在開發中,往往由于業務需要需要在代碼中添加一些瑣碎的、跟主要業務邏輯無關的東西,使用這兩者都有局限性,使用方法交換動態給指定的方法添加代碼以達到解耦的效果。
Method Swizzling原理
每個類都維護一個方法(Method)列表,Method則包含SEL和其對應IMP的信息,方法交換做的事情就是把SEL和IMP的對應關系斷開,并和新的IMP生成對應關系。(IMP有點類似函數指針,指向具體的Method實現)
交換前:Asel->AImp Bsel->BImp
交換后:Asel->BImp Bsel->AImp
Method Swizzling相關函數介紹
//獲取通過SEL獲取一個方法
class_getInstanceMethod
//獲取一個方法的實現
method_getImplementation
//獲取一個OC實現的編碼類型
method_getTypeEncoding
//給方法添加實現
class_addMethod
//用一個方法的實現替換另一個方法的實現
class_replaceMethod
//交換兩個方法的實現
method_exchangeImplementations
Method Swizzling實現的過程
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//case1: 替換實例方法
Class selfClass = [self class];
//case2: 替換類方法
Class selfClass = object_getClass([self class]);
//源方法的SEL和Method
SEL oriSEL = @selector(viewWillAppear:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
//交換方法的SEL和Method
SEL cusSEL = @selector(customViewWillApper:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
//先嘗試給源方法添加實現,這里是為了避免源方法沒有實現的情況
BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
//添加成功:將源方法的實現替換到交換方法的實現
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
//添加失敗:說明源方法已經有實現,直接將兩個方法的實現交換即可
method_exchangeImplementations(oriMethod, cusMethod);
}
});
Method Swizzling注意事項
1、方法交換應該保證唯一性和原子性
唯一性:應該盡可能在+load方法中實現,這樣可以保證方法一定會調用且不會出現異常。
原子性:使用dispatch_once來執行方法交換,這樣可以保證只運行一次。
2、一定要調用原始實現
由于iOS的內部實現對我們來說是不可見的,使用方法交換可能會導致其代碼結構改變,而對系統產生其他影響,因此應該調用原始實現來保證內部操作的正常運行。
3、方法名必須不能產生沖突
這個是常識,避免跟其他庫產生沖突。
4、做好記錄
記錄好被影響過的方法,不然時間長了或者其他人debug代碼時候可能會對一些輸出信息感到困惑。
5、如果非迫不得已,盡量少用方法交換
雖然方法交換可以讓我們高效地解決問題,但是如果處理不好,可能會導致一些莫名其妙的bug。
相關使用例子見下面的鏈接~