浅谈 KVO 原理

概述

  1. KVO(key value observing)允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。一般继承自NSObject的对象都支持KVO

  2. KVONSNotificationCenter都是iOS中观察者模式的一种实现,区别如下。

    • 在于观察者和被观察者之间,KVO是一对一,NSNotificationCenter是一对多的关系。
    • KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听。

基本使用

分三步

  1. 注册观察者。addObserver: forKeyPath: options: context:

     第一个参数observer:监听者
     第二个参数keyPath:要监听的属性
     第三个参数options:
         NSKeyValueObservingOptionNew 新值
         NSKeyValueObservingOptionOld 旧值
         NSKeyValueObservingOptionInitial 初始值
         NSKeyValueObservingOptionPrior 在属性发生更改之前和之后收到两次通知,而不是改变之后才收到一次通知
     第四个参数context:传入任意类型的对象,是kvo的一种传值方式
    

    调用此方法后,不会对观察者形成强引用。所以要注意观察者的释放时机,防止出现因观察者被释放导致crash

  2. 在观察者中实现回调方法。observeValueForKeyPath: ofObject: change: context:

    有多种方式都会触发KVO。

     // 点语法
     self.car.carName = @"丰田FJ";
     // setter方法
     [self.car setCarName:@"丰田FJ"];
     // setValue: forKey:
     [self.car setValue:@"丰田FJ" forKey:@"carName"];
     // setValue: forKeyPath:
     [self setValue:@"丰田FJ" forKeyPath:@"car.carName"];
    
  3. 移除观察者。removeObserver: forKeyPath:

  4.  

    KVO原理

    通过isa-swizzling技术实现。

  5. 在运行时创建一个原类的子类,有固定格式 NSKVONotifying_XXX

  6. 将对象的isa指针指向新创建的类,重写NSKVONotifying_XXX-class方法,返回原类的Class​​​​​​​

  7. 1.为了把流程看的明白一些,我们重写了一下类的- (NSString *)description方法
    - (NSString *)description {
        
        NSLog(@"obj is %p \n",self);
        
        IMP carNameIMP = class_getMethodImplementation(object_getClass(self), @selector(setCarName:));
        NSLog(@"IMP setCarName is %p \n",carNameIMP);
    
        Class objClass = [self class];
        NSLog(@"obj class is %@ \n", objClass);
        
        Class objRuntimeClass = object_getClass(self);
        NSLog(@"obj runtime class is %@ \n", objRuntimeClass);
    
        NSLog(@"obj method list is: \n");
        unsigned int count;
        Method *methodList = class_copyMethodList(objRuntimeClass, &count);
        for (NSInteger i = 0; i < count; i++) {
            Method method = methodList[i];
            NSString *methodName = NSStringFromSelector(method_getName(method));
            NSLog(@"method%ld = %@ \n", (long)i, methodName);
        }
        
        NSLog(@"\n");
        
        return @"";
    }
    
     2. 在添加监听者前后分别调用`description`方法
    
     [self.car description]; 
    
     // 添加监听者
     [self.car addObserver:self forKeyPath:@"carName" options:(NSKeyValueObservingOptionNew) context:@"我想传啥就传啥 nil也行"];
    
     [self.car description];
    
     3. 打印结果如下
    
     // 注册监听者之前调用的 description
     obj is 0x281368880
     IMP setCarName is 0x1001cbe3c
     obj class is USTestCar
     obj runtime class is USTestCar
     obj method list is:
     method0 = setCarName:
     method1 = carName
     method2 = .cxx_destruct
     method3 = description
    
     // 注册监听者之后调用的 description
     obj is 0x281368880
     IMP setCarName is 0x1f5a46340 // setter方法重写
     obj class is USTestCar
     obj runtime class is NSKVONotifying_USTestCar // 生成的子类
     obj method list is:
     method0 = setCarName:
     method1 = class
     method2 = dealloc
     method3 = _isKVOA // KVO自动生成的子类标识
    
  8. 子类重写setter方法。在修改值之前调用 willChangeValueForKey:;修改值之后调用didChangeValueForKey,这两个方法最终都会被调用到observeValueForKeyPath: ofObject: change: context:这个回调方法中

     可以自己调用`willChangeValueForKey`和`didChangeValueForKey`。即可在不改变属性值的情况下手动触发KVO。
    
     手动触发KVO 这两个方法必须同时调用。
     [self.car willChangeValueForKey:@"carName"];
     [self.car didChangeValueForKey:@"carName"];
  9. 4.最后在- (void)dealloc中移除KVO

  10. - (void)dealloc {
        [self.car removeObserver:self forKeyPath:@"carName"];
        [self.car description];
       
    }

    打印结果如下:

  11. obj is 0x600003260c00
    IMP setCarName is 0x10fb9bdb0
    obj class is USTestCar
    obj runtime class is USTestCar
    obj method list is:
    method0 = .cxx_destruct
    method1 = description
    method2 = setCarName:
    method3 = carName

    衍生出来的NSKVONotifying_USTestCar已经“销毁”,setter方法也都恢复,指针指向了原类。整个KVO的流程结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值