iOS KVO使用小坑

作者: tongxyj | 来源:发表于2018-05-28 11:14 被阅读20次

    这个版本接手selfStrong的代码的时候,在移除KVO的observer的时候一直挂,经排查发现,原来这个KVO是在父类中注册的和移除的,在某一时刻有个子类的实例被释放了,走到了父类的dealloc方法里,就挂了,因为子类并没有注册这个KVO,写了个demo测试下,顺便也看看通知会不会有这样的情况:

    @interface Father : NSObject
    - (void)addKVO;
    - (void)addNotification;
    @end
    
    #import "Father.h"
    
    @implementation Father
    
    - (void)addKVO {
        [self addObserver:self forKeyPath:@"testKVOKey" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    }
    - (void)addNotification {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(testNoti) name:@"testNoti" object:nil];
    }
    - (void)dealloc {
        [self removeObserver:self forKeyPath:@"testKVOKey"];
         [[NSNotificationCenter defaultCenter] removeObserver:self name:@"testNoti" object:nil];
    }
    

    有个父类Father,在Father类中注册了一个KVO和通知,并在dealloc方法中移除对应的KVO和通知。

    #import "Father.h"
    
    @interface Son : Father
    @end
    #import "Son.h"
    
    @implementation Son
    @end
    

    还有个子类Son,啥也没干。

    #import "ViewController.h"
    #import "Son.h"
    @interface ViewController ()
    @property (nonatomic, strong) Father *father;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.father = [[Father alloc] init];
        [self.father addKVO];//注册KVO
        [self.father addNotification];//注册通知
        Son *son = [[Son alloc] init];
    }
    @end
    

    在VC中分别创建了父类和子类的实例,并且注册了父类的KVO和通知。
    程序运行,奔溃,控制台报错:
    *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <Son 0x6000002095e0> for the key path "testKVOKey" from <Son 0x6000002095e0> because it is not registered as an observer.'
    原因很明显,Son被释放,调用了Father的dealloc,移除了一个没有注册的KVO,就会奔溃,但通知却没有这个问题,另外KVO移除多次或者没有移除监听也会奔溃。对于通知,多次移除也不会奔溃,并且在iOS9之后就不需要我们手动移除了。
    最后在Father的dealloc方法中判断了一下:

    - (void)dealloc {
        if ([self isMemberOfClass:[Father class]]) {
            [self removeObserver:self forKeyPath:@"testKVOKey"];
            [[NSNotificationCenter defaultCenter] removeObserver:self name:@"testNoti" object:nil];
        }
    }
    

    只在Father的dealloc中移除响应的KVO和通知,目前没有发现新的问题。这个问题当时排查了一阵没有头绪,其实仔细看控制台的打印就能发现,没有注册的类是子类而不是父类,最后定位到了问题点。

    相关文章

      网友评论

        本文标题:iOS KVO使用小坑

        本文链接:https://www.haomeiwen.com/subject/iliwjftx.html