iOS KVO 常见错误

一、KVO

  Key-Value Observing 键值用于检测对象的某些属性的实时变化情况并作出响应

KVO实现

/** KVO 实现 */
self.womanPerson = [[WLPerson alloc] init];
[self.womanPerson addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.womanPerson.name = @"rose";

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    /** 同一个类里可能有多个KVO */
    if (object == self.person && [keyPath isEqualToString:@"name"]) {
        WLPerson *person = object;
        NSLog(@"---->%@", person.name);
    } else if (object == self.womanPerson && [keyPath isEqualToString:@"name"]) {
        WLPerson *person = object;
        NSLog(@"---->%@", person.name);
    }
}

在为一个对象添加观察者之时,framework使用runtime动态创建了一个WLPerson类的子类NSKVONotifying_WLPerson,而为了不让外部知道这一行为,NSKVONotifying_WLPerson重写了-(void)class方法返回之前的类

self.person = [[WLPerson alloc] init];
NSLog(@"
-添加监听者之前-.class-->%@  
            ---isa--->%s", NSStringFromClass(self.person.class), object_getClassName(self.person));
object_getClass(self.person);

[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"
-添加监听者之后-.class-->%@  
            ---isa--->%s", NSStringFromClass(self.person.class), object_getClassName(self.person));

[self.person removeObserver:self forKeyPath:@"name" context:nil];
NSLog(@"
-移除监听者之后-.class-->%@  
            ---isa--->%s", NSStringFromClass(self.person.class), object_getClassName(self.person));


打印:
-添加监听者之前-.class-->WLPerson  
             ---isa--->WLPerson
-添加监听者之后-.class-->WLPerson  
             ---isa--->NSKVONotifying_WLPerson
-移除监听者之后-.class-->WLPerson  
             ---isa--->WLPerson

可以看到在添加观察之后.class打印出来的类没有变化,使用object_getClassName()打印出来的类是NSKVONotifying_WLPerson ,因为这个object_getClass()返回的是这个对象的isa指针,isa指针指向的一定是这个对象所属的类。

二、常见错误

1、romove 观察者

[self removeObserver:self forKeyPath:@"name" context:nil];
*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x121d099d0> for the key path "name" from <ViewController 0x121d099d0> because it is not registered as an observer.'

原因:crash信息已经很清楚了,谁添加的观察者,谁移除

2、重复移除观察者

[self removeObserver:self forKeyPath:@"name" context:nil];
[self removeObserver:self forKeyPath:@"name" context:nil];

crash *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x100306e50> for the key path "name" from <WLPerson 0x17400a740> because it is not registered as an observer.’

解决:网上有很多方法,最简单的try catch 
    @try {
        [self.person removeObserver:self forKeyPath:@"name" context:nil];
        [self.person removeObserver:self forKeyPath:@"name" context:nil];
    } @catch (NSException *exception) {
        NSLog(@"重复移除");
    } @finally {
        
    }
可能有人会问了,谁这么傻逼移除两次,其实在很多情况下确实写的移除一次,但是可能。。。。。比如SDPhotobrowser就存在这样的crash

3、属性的值修改了的信息收到了,但是没有处理 方法-observeValueForKeyPath:ofObject:change:context:却没有实现,只要你注册了观察者,这个方法必须实现

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<ViewController: 0x10040a1e0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: name
Observed object: <WLPerson: 0x170005d00>
Change: {
    kind = 1;
    new = jack;
}
Context: 0x0

4、添加和移除时,content上下文不一致

[self.person removeObserver:self forKeyPath:@"name" context:@"随便给个"];
*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x100704960> for the key path "name" from <WLPerson 0x1740046f0> because it is not registered as an observer.’

ps:一般传nil

5、给局部变量添加观察者

WLPerson *tempPerson = [[WLPerson alloc] init];
[tempPerson addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
tempPerson.name = @"jack”;
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x174000880 of class WLPerson was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x174022800> (
<NSKeyValueObservance 0x174045a00: Observer: 0x100307e70, Key path: name, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x174045be0>
)'

未完待续。。。

原文地址:https://www.cnblogs.com/10-19-92/p/4955950.html