zoukankan      html  css  js  c++  java
  • [crash详解与防护] KVO crash

    一、KVO介绍

          KVO(Key-Value Observing),键值监听。它提供一种机制:指定的被观察者的属性被改变后,KVO就会通知观察者,观察者可以做出响应。

      KVO作用:利用KVO,很容易实现视图组件和数据模型的分离。当数据模型的属性值改变之后,作为监听者的视图组件就会被激发。这有利于业务逻辑和视图展示的解耦合。

          KVO使用步骤:(1)注册观察,添加观察者及属性;(2)实现回调方法;(3)移除观察。

     (1)注册观察:

    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
    /*
       observer:观察者,也就是KVO通知的订阅者。订阅着必须实现observeValueForKeyPath:ofObject:change:context:方法
      keyPath:描述将要观察的属性,相对于被观察者。 
      options:KVO的一些属性配置;有四个选项。 
           options所包括的内容:
             NSKeyValueObservingOptionNew:change字典包括改变后的值;
             NSKeyValueObservingOptionOld:   change字典包括改变前的值; 
             NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知; 
             NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次).
      context: 上下文,这个会传递到订阅着的函数中,用来区分消息,所以应当是不同的。 
    */

    (2)实现回调方法:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
      /*
        keyPath:被监听的keyPath , 用来区分不同的KVO监听. 
         object: 被观察修改后的对象(可以通过object获得修改后的值). 
         change:保存信息改变的字典(可能有旧的值,新的值等) .
         context:上下文,用来区分不同的KVO监听.
    */

    (3)移除观察

     - (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath
      - (void)removeObserver:(NSObject *)observer     forKeyPath:(NSString *)keyPath  context:(void *)context
     /*
         注意:不要忘记解除注册,否则会导致资源泄露 .
     */

    二、KVO使用举例及注意事项

    //被观察者 StockData.m
    #import "StockData.h"
    @interface StockData()
    @property(nonatomic, strong)NSString *stockName;
    @property(nonatomic, strong)NSString *price;
    @end
    
    //观察者 SLVKVOController.m
    #import "SLVKVOController.h"
    #import "StockData.h"
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self.stockData setValue:@"searph" forKey:@"stockName"];
        [self.stockData setValue:@"10.0" forKey:@"price"];
        [self.stockData addObserver:self forKeyPath:@"price"  options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:SLVKVOContext];
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if(context == SLVKVOContext && object == self.stockData && [keyPath isEqualToString:@"price"]) {
            NSString * oldValue = [change objectForKey:NSKeyValueChangeOldKey];
            NSString * newValue = [change objectForKey:NSKeyValueChangeNewKey];
            self.myLabel.text = [NSString stringWithFormat:@"oldValue:%@ , newValue:%@",oldValue,newValue];
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    
    -(void)dealloc {
        [self.stockData removeObserver:self forKeyPath:@"price" context:SLVKVOContext];
    }

    注意:

    (1)在第二步回调observeValueForKeyPath:函数中,要用else进行判断调用super的对应函数。因为若当前函数无法处理对应的kvo,有可能super-class会有一些kvo的对应处理。

    (2)在第三步在dealloc函数中注销观察中,当对同一个keypath进行两次removeObserver时会导致程序crash,这种情况常常出现在父类有一个kvo,父类在dealloc中remove了一次,子类又remove了一次的情况下。可以利用context字段来标识出到底kvo是superClass注册的,还是self注册的。我们可以分别在父类以及本类中定义各自的context字符串,然后在dealloc中remove observer时指定移除的自身添加的observer。这样就能避免二次remove造成crash。

    三、KVO常见crash及防护方案

    KVO常见crash类型:

    (1)不能对不存在的属性进行kvo观测,否则会报crash:uncaught exception 'NSUnknownKeyException', reason: '[<StockData 0x600000203d50> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key stockName.'

    (2)订阅者必须实现 observeValueForKeyPath:ofObject:change:context:方法,否则crash。

     Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<SLVKVOController: 0x7f811372ff70>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.

    (3) 移除观察,超过addObserver的次数就会 crash:Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <SLVKVOController 0x7ff8e8703100> for the key path "price" from <StockData 0x60800003d000> because it is not registered as an observer.'

     KVO crash解决方案:

     方案一、

      可以让被观察对象持有一个KVO的delegate,所有和KVO相关的操作均通过delegate来进行管理,delegate通过建立一张map来维护KVO整个关系。

    中间层delegate的代理工作:

    (1)如果出现KVO重复添加观察者或者重复移除观察者(KVO注册观察者与移除观察者不匹配)的情况,delegate可以直接阻止这些非正常的操作。

    (2)被观察者dealloc之前,可以通过delegate自动将与自己有关的KVO关系都注销掉,避免了KVO的被观察者dealloc时仍然注册着KVO导致的crash。

    方案二、

      我们可以让观察者在注册的过程中,将注册信息一同记录下来,然后使用某种方法在对象dealloc时,在记录的信息里找到对应的观察者,注销观察。

      此方案在宿主释放过程中嵌入我们自己的对象,使得宿主释放时顺带将我们的对象一起释放掉,从而获取dealloc的时机点。采用构建一个释放通知对象,通过AssociatedObject方式连接到宿主对象,在宿主释放时进行回调,完成注销动作。

         具体的原理和代码可以参照上一篇文章《[crash详解与防护] NSNotification crash》。

     

           

  • 相关阅读:
    Restful接口传入多参数
    map转换成JSON的3种方式
    项目打包后执行start.sh提示“no such file or directory”解决办法,linux中给文件增加权限
    如何将一个a表a1字段与b表b1字段的笛卡尔积插入到e表中
    get、set方法的取代注释之lombok插件
    推荐 33 个 IDEA 最牛配置转(Java技术栈)
    mysql-----group by 对多个字段进行分组
    mysql获取某段时间内每一天的统计数据
    发票流水号生成方式
    postman之post请求传参
  • 原文地址:https://www.cnblogs.com/Xylophone/p/6394051.html
Copyright © 2011-2022 走看看