自定义isa-swizzling与KVO的冲突

127 阅读1分钟

最近在项目中需要对一些对象做isa-swizzling,并且重写掉原始类中的所有setter、getter方法。一开始使用isa-swizzling也挺顺利的,直到遇到了KVO。

众所周知,KVO的实现也是isa-swizzling。所以可能和自定isa-swizzling kennel存在冲突,但是没有找到具体原因。

所以如果有大佬知道该怎么解决,我愿意献上我的膝盖

代码如下:

ViewController

#import "ViewController.h"
#import "NSObject+Hook.h"

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation Person

@end

@interface ViewController ()

@property (nonatomic, strong) Person *person;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    /// 对象先做一次KVO
    /// 然后对KVO后的对象在做一次 isa-swizzling
    /// 这时候修改name,KVO会崩溃
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    [self.person hook:@selector(setTip:)];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.person.name = @"123";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"%@", change);
}

- (Person *)person
{
    if (!_person) {
        _person = [[Person alloc] init];
    }
    return _person;
}

@end

NSObject+Hook

#import "NSObject+Hook.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation NSObject (Hook)

- (void)hook:(SEL)cmd
{
    Class class = object_getClass(self);
    NSString *subClassName = [NSString stringWithFormat:@"HOOK_%@", NSStringFromClass(class)];
    Class subClass = objc_allocateClassPair(class, subClassName.UTF8String, 0);
    objc_registerClassPair(subClass);
    object_setClass(self, subClass);
    
    Method method = class_getInstanceMethod(subClass, cmd);
    class_addMethod(subClass, cmd, (IMP)setterMethod, method_getTypeEncoding(method));
}

void setterMethod(id self, SEL _cmd, id obj)
{
    struct objc_super superClass = {
        self,
        class_getSuperclass(object_getClass(self))
    };
    objc_msgSendSuper(&superClass, _cmd, obj);
}

@end

运行崩溃如下图:

image.png

git仓库: