[crash详解与防御] KVO crash

1、KVO介绍app

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

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

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

 (1)注册观察:spa

- (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)实现回调方法:3d

- (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
 /*
     注意:不要忘记解除注册,不然会致使资源泄露 .
 */

2、KVO使用举例及注意事项code

//被观察者 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];
}

注意:orm

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

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

3、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解决方案:

 方案1、

  可让被观察对象持有一个KVO的delegate,全部和KVO相关的操做均经过delegate来进行管理,delegate经过创建一张map来维护KVO整个关系。

中间层delegate的代理工做:

(1)若是出现KVO重复添加观察者或者重复移除观察者(KVO注册观察者与移除观察者不匹配)的状况,delegate能够直接阻止这些非正常的操做。

(2)被观察者dealloc以前,能够经过delegate自动将与本身有关的KVO关系都注销掉,避免了KVO的被观察者dealloc时仍然注册着KVO致使的crash。

方案2、

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

  此方案在宿主释放过程当中嵌入咱们本身的对象,使得宿主释放时顺带将咱们的对象一块儿释放掉,从而获取dealloc的时机点。采用构建一个释放通知对象,经过AssociatedObject方式链接到宿主对象,在宿主释放时进行回调,完成注销动做。

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

相关文章
相关标签/搜索