前言:Method Swizzling是利用Objective C動(dòng)態(tài)替換方法的IMP(執(zhí)行函數(shù))姐叁。常用的場(chǎng)景有兩個(gè):1.Debug某些復(fù)雜函數(shù); 2.在某無法修改源代碼的SDK中插入一段代碼。 GitHub上注明的響應(yīng)式和函數(shù)式編程庫ReactiveCocoa就是利用這一技術(shù)寫的庫。
幾個(gè)定義Selectors, Methods, 和 Implementations
Selectors: 是一個(gè)C類型的字符串撞秋,Runtime根據(jù)這個(gè)C的字符串map到對(duì)應(yīng)的方法
Methods: 透明的類型表示,表示一個(gè)Class中方法的定義
Implementation (typedef id (*IMP)(id, SEL, …)):就是實(shí)際的執(zhí)行C函數(shù)琼讽。IMP指向?qū)嶋H的方法執(zhí)行體徘熔,id表示消息的接受者,SEL則是Selectors名字霉猛,后邊的…表示不確定的傳入?yún)?shù)
如何進(jìn)行Method Swizzling尺锚?
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(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);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
交換前的圖解
交換之后
所以,交換后的Runtime順序
- 根據(jù)@selector(viewWillAppear:)找到自定義的xxx_viewWillAppear實(shí)現(xiàn)
- 自定義的xxx_viewWillAppear實(shí)現(xiàn)惜浅,向Self發(fā)送@selector(xxx_viewWillAppear)
- 根據(jù)@selector(xxx_viewWillAppear)缩麸,找到默認(rèn)的SDK中viewWillAppear的實(shí)現(xiàn)
- 執(zhí)行默認(rèn)SDK中viewWillAppear代碼
- 執(zhí)行NSLog(@"viewWillAppear: %@", self);
- 結(jié)束
+load 和 +initialize兩個(gè)方法
Method Swizzling要在+load進(jìn)行
這兩個(gè)方法都是Runtime中自動(dòng)調(diào)用的方法。其中
- +load在每個(gè)類被裝載到Runtime的時(shí)候調(diào)用
- +initialize 在每個(gè)類第一次被發(fā)送消息的時(shí)候調(diào)用。
之所以要在+load中進(jìn)行杭朱,是因?yàn)榉椒ń粨Q影響的是全局狀態(tài)阅仔,+load中能保證在class 裝載的時(shí)候進(jìn)行交換,而initialize沒辦法做到弧械。
進(jìn)行Method Swizzling的注意事項(xiàng)
最重要的一點(diǎn)八酒,理解Runtime的機(jī)制,理解交換帶來的影響刃唐,千萬不要僅僅拷貝黏貼羞迷,因?yàn)楹芸赡苣阋粋€(gè)不合理的交換導(dǎo)致整個(gè)程序的沒辦法正常運(yùn)轉(zhuǎn)
- 一定要調(diào)用最初的交換之前的方法(除非有很好的理由不調(diào)用),因?yàn)樽畛醯姆椒ㄏ鄬?duì)較差的方法是透明的画饥,調(diào)用最初的方法保證在Swizzling是插入一段代碼衔瓮,而不是替換整個(gè)代碼
- 給方法名字起前綴,防止和其他現(xiàn)在和未來可能出現(xiàn)的方法沖突
- 在+load中進(jìn)行
- dispatch_once保證只swizzling一次抖甘,很明顯热鞍,交換多次,你也不清楚到底交換成功了沒
- 不管你對(duì)自己的技術(shù)都么有信心衔彻,記住薇宠,你是基于當(dāng)前規(guī)則,當(dāng)前Runtime做的艰额。很可能Apple在下一版本改變了玩法澄港,你之前的就不能用了,要有這個(gè)心里準(zhǔn)備
Method Swizzling的缺點(diǎn)
優(yōu)點(diǎn)很明顯柄沮,你可以任意的在SDK中插入代碼回梧,可以改變方法的執(zhí)行體。
這里主要列出缺點(diǎn)
- 容易被濫用-除非自己理由充分祖搓,否則不建議使用
- 難以理解-經(jīng)常會(huì)出現(xiàn)-(void)funciton{[self function];}等類似無限循環(huán)的地方
- 難以Debug漂辐,在方法交叉后,Debug的調(diào)用堆棧會(huì)變的十分奇怪
- 潛在的命名沖突棕硫,這個(gè)上文提到了髓涯,要加上前綴,但是仍然不能百分百保證不沖突
有時(shí)間的可以看看StackOverFlow上這個(gè)回答哈扮,講的很好