這個 Tip 來源于一道面試題得糜,感覺很是考察知識變通的能力仗扬,對 KVO 深入了解的同學(xué)唉侄,應(yīng)該很容易就可以答出來悠垛。這里拋磚引玉线定,簡單聊聊這個 Tip
首先簡單總結(jié)下 KVO 的大概原理
- 當(dāng)你觀察一個對象時,會動態(tài)的創(chuàng)建該對象類的子類确买,這個子類重寫了被觀察屬性的 setter 方法斤讥,同時將該對象的 isa 指針指向了新創(chuàng)建的子類。在 Objective-C 中對象是通過 isa 指針來查找對應(yīng)類中的方法列表的湾趾,所以這里可以把該對象看為新子類的實例對象芭商。重寫的 setter 方法會在調(diào)用原 setter 方法之后,通知觀察者對象屬性值的更改搀缠。
好的铛楣,下面進(jìn)入正題,聊聊如何為一個實例動態(tài)替換方法胡嘿。
首先創(chuàng)建一個初始化工程蛉艾,直接對 ViewController 進(jìn)行實戰(zhàn)即可,在 ViewController 中加入一個 eat 方法,如下
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
- (void)eat {
NSLog(@"original eat");
}
然后寫一個 NSObject 的 Category 負(fù)責(zé)進(jìn)行方法交換勿侯,將原對象的 isa 指針指向該對象類的子類(LDSubclass)拓瞪,并在子類中重寫 eat 方法
@implementation NSObject (Hook)
+ (void)hookWithInstance:(id)instance method:(SEL)selector {
Method originMethod = class_getInstanceMethod([instance class], selector);
if (!originMethod) {
// exception ..
}
Class newClass = [LDSubclass class];
// 修改 isa 指針的指向
object_setClass(instance, newClass);
}
@end
子類的代碼很簡單,就是重寫 eat 方法助琐,如果有需要祭埂,可以調(diào)用原方法的實現(xiàn)
@implementation LDSubclass
- (void)eat {
NSLog(@"newSubClass eat");
struct objc_super superClazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 調(diào)用原方法
void (*objc_msgSendSuperCasted)(void *, SEL) = (void *)objc_msgSendSuper;
objc_msgSendSuperCasted(&superClazz, _cmd);
}
@end
最后在 ViewControlller 中進(jìn)行測試即可,此時的 ViewController 代碼如下
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
ViewController * vc = [[ViewController alloc] init];
[vc eat];
NSLog(@"-----------");
ViewController * hookedInstance= [[ViewController alloc] init];
[ViewController hookWithInstance:hookedInstance method:@selector(eat)];
[hookedInstance eat];
}
- (void)eat {
NSLog(@"original eat");
}
@end
來看看打印的結(jié)果兵钮,第一個沒有 Hook 的實例蛆橡,正常執(zhí)行;第二個Hook 后的實例掘譬,先執(zhí)行重寫的方法泰演,后執(zhí)行原方法。
2017-03-20 14:30:21.244 JYHookInstanceMethod[91153:3422584] original eat
2017-03-20 14:30:21.244 JYHookInstanceMethod[91153:3422584] -----------
2017-03-20 14:30:21.245 JYHookInstanceMethod[91153:3422584] newSubClass eat
2017-03-20 14:30:21.245 JYHookInstanceMethod[91153:3422584] original eat
原理
與 KVO 的 isa-swizzling 思路相同葱轩,對想要 Hook 實例的類創(chuàng)建一個子類睦焕,并在子類中重寫想要 Hook 的方法,將該實例的 isa 指針指向子類靴拱,這樣進(jìn)行方法查找時垃喊,便會在子類方法列表進(jìn)行查找,如果想要執(zhí)行更多操作袜炕,可以在替換后的新方法中加入自己的邏輯本谜。
這里只是一個超級簡單的 Demo,很多邊界情況沒有考慮偎窘,后期可以自己完善乌助,Demo 可以參考JYHookInstanceMethod