這個 Tip 來源于一道面試題,感覺很是考察知識變通的能力,對 KVO 深入了解的同學(xué),應(yīng)該很容易就可以答出來。這里拋磚引玉,簡單聊聊這個 Tip
首先簡單總結(jié)下 KVO 的大概原理
- 當(dāng)你觀察一個對象時(shí),會動態(tài)的創(chuàng)建該對象類的子類,這個子類重寫了被觀察屬性的 setter 方法,同時(shí)將該對象的 isa 指針指向了新創(chuàng)建的子類。在 Objective-C 中對象是通過 isa 指針來查找對應(yīng)類中的方法列表的,所以這里可以把該對象看為新子類的實(shí)例對象。重寫的 setter 方法會在調(diào)用原 setter 方法之后,通知觀察者對象屬性值的更改。
好的,下面進(jìn)入正題,聊聊如何為一個實(shí)例動態(tài)替換方法。
首先創(chuàng)建一個初始化工程,直接對 ViewController 進(jìn)行實(shí)戰(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)用原方法的實(shí)現(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)行測試即可,此時(shí)的 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 的實(shí)例,正常執(zhí)行;第二個Hook 后的實(shí)例,先執(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 實(shí)例的類創(chuàng)建一個子類,并在子類中重寫想要 Hook 的方法,將該實(shí)例的 isa 指針指向子類,這樣進(jìn)行方法查找時(shí),便會在子類方法列表進(jìn)行查找,如果想要執(zhí)行更多操作,可以在替換后的新方法中加入自己的邏輯。
這里只是一個超級簡單的 Demo,很多邊界情況沒有考慮,后期可以自己完善,Demo 可以參考JYHookInstanceMethod