用runtime实现一个KVO

1 KVO的原理

当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个对象的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值得改变。此外,派生类还重写了 dealloc 方法来释放资源。

用runtime实现一个KVO_第1张图片
111.png

2动手实现一个KVO

假设有一个Person类


@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *age;
@end

@implementation Person
@end

创建一个NSObject+MyKVO的分类,然后再.m文件中加入以下代码

- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    //给新类取名字
    NSString *currentClassName = NSStringFromClass([self class]);
    NSString *createdClassName = [@"MyKVO" stringByAppendingString:currentClassName];
    const char * newname = [createdClassName UTF8String];
    //用runtime创建一个类,创建完类要注册才能用。第一个参数表示新类继承谁
    Class newClass = objc_allocateClassPair([self class], newname, 0);
    objc_registerClassPair(newClass);
    //添加一个name的set方法
    class_addMethod(newClass, @selector(setName:), (IMP)setName, "v@:@");
    //设置一个对象的类(即改编isa指针),也就是说当前这个对象现在是新类的实例
    object_setClass(self, newClass);
     const void *key = "qwer";
    //添加一个成员变量名字叫objc,值就是观察者observer。
    objc_setAssociatedObject(self, key, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//我们要在setName调用super 的set 方法还要通知外界
void setName(id self, SEL _cmd, NSString * newName){
    //保存新类
    id class = [self class];
    //先把isa指针指向它的父类(就是原来那个类)
    object_setClass(self, class_getSuperclass(class));
    //让父类调用setName方法
    objc_msgSend(self, @selector(setName:),newName);
    //拿到observer
    id objc = objc_getAssociatedObject(self, "qwer");
    //调用observer的observeValueForKeyPath方法,参数1:person本身,2要监听的name
    objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),self,@"name",nil,nil);
    //改回新类类型
    object_setClass(self, class);
}

此时在VC中就能监听name的改变了

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [[Person alloc]init];
    [self.person my_addObserver:self forKeyPath:@"name" options:0 context:nil];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"监听到了%@",_person.age);
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    static int a = 1;
    a++;
    self.person.name = [NSString stringWithFormat:@"%d",a];
}

3 完善

这个时候MyKVO只能监听name属性,还不能监听其他属性,比如不能监听age,下面修改完善NSObject+MyKVO

//转换被监听的keyPath为SEL
-(SEL)convertKeyPathToSEL:(NSString*)keyPath{
    NSString *firstUp = [[keyPath uppercaseString]substringToIndex:1];
    NSString *otherStr = [keyPath substringFromIndex:1];
    NSString *SELString = [[@"set" stringByAppendingString:[firstUp stringByAppendingString:otherStr]]stringByAppendingString:@":"];
    return NSSelectorFromString(SELString);
}
- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    NSString *currentClassName = NSStringFromClass([self class]);
    NSString *createdClassName = [@"MyKVO" stringByAppendingString:currentClassName];
    const char * newname = [createdClassName UTF8String];
    Class newClass = objc_allocateClassPair([self class], newname, 0);
    objc_registerClassPair(newClass);
    //添加一个被监听属性的set方法,IMP是setName
    class_addMethod(newClass,[self convertKeyPathToSEL:keyPath], (IMP)setName, "v@:@");
    object_setClass(self, newClass);
    const void *key = "myObserver";
    const void *mykeyPath = "myKeyPath";
    objc_setAssociatedObject(self, key, observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    objc_setAssociatedObject(self, mykeyPath, keyPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//我们要在setName调用super 的set 方法还要通知外界
void setName(id self, SEL _cmd, NSString * newName){

    NSString *keyPath = objc_getAssociatedObject(self, "myKeyPath");
    id class = [self class];
    object_setClass(self, class_getSuperclass(class));
    //让父类调用set方法
    objc_msgSend(self, [self convertKeyPathToSEL:keyPath],newName);
    id objc = objc_getAssociatedObject(self, "myObserver");
    objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,nil,nil);
    object_setClass(self, class);
}

你可能感兴趣的:(用runtime实现一个KVO)