KVO的使用及底层探究

KVO的使用

KVO使用起来非常简单,三个步骤就搞定啦

1、通过addObserver: forKeyPath: options: context方法注册成为观察者,这样就可以观察到keyPath属性变化事件
2、实现observeValueForKeyPath: ofObject: change:context:方法,当属性值发生变化,KVO会回调这个方法通知观察者
3、当不需要监听的时候调用removeObserver: forKeyPath将KVO移除

KVO的触发模式

KVO有两种触发模式,手动和自动(不设置默认为自动)
来看看手动怎么触发

//在观察者的类中实现下面这个方法
// 1、模式调整 返回YES为自动 NO为手动
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return YES;
}
//2、手动触发KVO
 [_p willChangeValueForKey:@"name"];
 _p.name = [NSString stringWithFormat:@"%d",a++];
 [_p didChangeValueForKey:@"name"];

手动触发的好处就是我们可以根据需求的不同来决定要不要触发KVO

KVO观察对象属性

假设我们有一个Dog类,类里面有age、level两个属性,那么我们怎么使用KVO观察对象呢?

//在观察者的类中实现下面这个类方法
+(NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    NSSet* keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"dog"]) {
        keyPaths = [[NSSet alloc]initWithObjects:@"_dog.age",@"_dog.level", nil];
    }
    return keyPaths;
}

//注册观察者 观察dog属性
[_p addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil];

简直是 so easy 有木有啊!

不过,作为一名程序员,就要有打破砂锅问到底的精神,KVO究竟是怎么实现的呢?

下面一起来揭开KVO的神秘面纱,let's go

KVO原理探究

KVO的内部实现分以下三个步骤

1、创建一个子类(为什么KVO的实现使用继承而不使用分类?因为KVO会重写set方法,而使用分类重写set方法会覆盖掉原来类的set方法)
2、重写set方法
3、外界改变isa指针

-(void)JYC_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    //1、创建一个类 父类为self即调用者
    NSString* oldClassName = NSStringFromClass([self class]);
    NSString* newClassName = [@"JYCkvo_" stringByAppendingString:oldClassName];
    Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
    objc_registerClassPair(myClass);//注册类
    
    //2. 重写set方法(实际上添加了set方法,子类中没有没有父类的方法,仅仅是可以调用) myclass
    class_addMethod(myClass, @selector(setName:), (IMP)setName, "V@:@");
    
    //3、修改isa指针(将调用者指向子类),这样调用者调用set方法会来到本类中重写的set方法中
    object_setClass(self, myClass);
    
    //4、将观察者保存到当前对象  OBJC_ASSOCIATION_ASSIGN 属性类型是weak,防止循环引用
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
}

void setName(id self, SEL _cmd ,NSString* newname){
    NSLog(@"---%@",newname);
    //    调用父类的setName:方法
    Class class = [self class];
    object_setClass(self, class_getSuperclass(class));//当前类改为父类
    objc_msgSend(self, @selector(setName:),newname);
    
    //    拿到观察者
    id observer = objc_getAssociatedObject(self, "observer");
    if(observer){
        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":newname,@"kind:":@1},nil);
    }
    
    //改回子类
    object_setClass(self, class);
    
}
⚠️任何OC方法的调用本质上就是消息发送msgsend,发送的时候会包含两个隐式参数,分别是调用者和方法编号。

你可能感兴趣的:(KVO的使用及底层探究)