這個 Tip 來源於一道面試題,感覺很是考察知識變通的能力,對 KVO 深入瞭解的同學,應該很容易就可以答出來。這裡拋磚引玉,簡單聊聊這個 Tip
首先簡單總結下 KVO 的大概原理
- 當你觀察一個物件時,會動態的建立該物件類的子類,這個子類重寫了被觀察屬性的 setter 方法,同時將該物件的 isa 指標指向了新建立的子類。在 Objective-C 中物件是通過 isa 指標來查詢對應類中的方法列表的,所以這裡可以把該物件看為新子類的例項物件。重寫的 setter 方法會在呼叫原 setter 方法之後,通知觀察者物件屬性值的更改。
好的,下面進入正題,聊聊如何為一個例項動態替換方法。
首先建立一個初始化工程,直接對 ViewController 進行實戰即可,在 ViewController 中加入一個 eat 方法,如下
1 2 3 4 5 6 7 8 9 |
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor redColor]; } - (void)eat { NSLog(@"original eat"); } |
然後寫一個 NSObject 的 Category 負責進行方法交換,將原物件的 isa 指標指向該物件類的子類(LDSubclass),並在子類中重寫 eat 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@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 方法,如果有需要,可以呼叫原方法的實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@implementation LDSubclass - (void)eat { NSLog(@"newSubClass eat"); struct objc_super superClazz = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)) }; // 呼叫原方法 void (*objc_msgSendSuperCasted)(void *, SEL) = (void *)objc_msgSendSuper; objc_msgSendSuperCasted(&superClazz, _cmd); } @end |
最後在 ViewControlller 中進行測試即可,此時的 ViewController 程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@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 |
來看看列印的結果,第一個沒有 Hook 的例項,正常執行;第二個Hook 後的例項,先執行重寫的方法,後執行原方法。
1 2 3 4 |
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 例項的類建立一個子類,並在子類中重寫想要 Hook 的方法,將該例項的 isa 指標指向子類,這樣進行方法查詢時,便會在子類方法列表進行查詢,如果想要執行更多操作,可以在替換後的新方法中加入自己的邏輯。
這裡只是一個超級簡單的 Demo,很多邊界情況沒有考慮,後期可以自己完善,Demo 可以參考JYHookInstanceMethod