iOS KVO使用小坑
2018-05-28 本文已影响20人
tongxyj
这个版本接手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和通知,目前没有发现新的问题。这个问题当时排查了一阵没有头绪,其实仔细看控制台的打印就能发现,没有注册的类是子类而不是父类,最后定位到了问题点。