toppic
当前位置: 首页> 玄幻小说> 如何为一个实例动态替换方法

如何为一个实例动态替换方法

2022-04-25 13:47:22

(点击上方公众号,可快速关注)

来源:伯乐在线 - Martin_wjl

如有好文章投稿,请点击 → 这里了解详情

如需转载,发送「转载」二字查看说明


这个 Tip 来源于一道面试题,感觉很是考察知识变通的能力,对 KVO 深入了解的同学,应该很容易就可以答出来。这里抛砖引玉,简单聊聊这个 Tip


首先简单总结下 KVO 的大概原理


  • 当你观察一个对象时,会动态的创建该对象类的子类,这个子类重写了被观察属性的 setter 方法,同时将该对象的 isa 指针指向了新创建的子类。在 Objective-C 中对象是通过 isa 指针来查找对应类中的方法列表的,所以这里可以把该对象看为新子类的实例对象。重写的 setter 方法会在调用原 setter 方法之后,通知观察者对象属性值的更改。


好的,下面进入正题,聊聊如何为一个实例动态替换方法。


首先创建一个初始化工程,直接对 ViewController 进行实战即可,在 ViewController 中加入一个 eat 方法,如下


- (void)viewDidLoad {

    [super viewDidLoad];

 

    self.view.backgroundColor = [UIColor redColor];

}

 

- (void)eat {

    NSLog(@"original eat");

}


然后写一个 NSObject 的 Category 负责进行方法交换,将原对象的 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 方法,如果有需要,可以调用原方法的实现


@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 代码如下


@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 后的实例,先执行重写的方法,后执行原方法。


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 可以参考

https://github.com/joy0304/JoyDemo/tree/master/JYHookInstanceMethod


Reference


KVO Implementation

https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html

如何自己动手实现 KVO

http://tech.glowing.com/cn/implement-kvo/


看完本文有收获?请分享给更多人

关注「 iOS大全 」,提升iOS技能


友情链接