最近写的JKCrashProtect的两篇文章得到了一些小伙伴的响应,一些小伙伴已经开始使用JKCrashProtect这个库了,很是开心。我今天在这里重点给大家分享一下有KVO造成的crash。
KVO产生crash的原因
相信大家用过KVO的应该比较多,KVO中的添加观察者,和移除观察者必须要成对出现,这个常识相信大家都是有的,所以某个人如果忘记了使用后移除已经添加的观察者造成了crash,这个不在我的考虑范围内。原因如下:JKCrashProtect是为了处理一些比较繁琐,同时容易造成crash的场景,但前提是不改变开发者的编码习惯,不能让开发者养成不好的开发习惯。既然观察者的添加和移除要成对的出现,那么如果不成对出现,就会出现crash现象。比如多线程下添加了相同的两个观察者,但移除的时候只移除了一个;或者添加的一个观察者,移除的时候由于多线程的原因却移除了两次。总之只要不是添加和移除不是成对的出现,那么就会出现crash。出现crash现象的实例代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
_jack = [JKPerson new];
[_jack addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[_jack addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
});
UIButton *button = [UIButton new];
button.frame = CGRectMake(0, 0, 80, 30);
button.center = self.view.center;
button.backgroundColor = [UIColor redColor];
[button setTitle:@"Click" forState:UIControlStateNormal];
[button addTarget: self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)dealloc{
[_jack removeObserver:self forKeyPath:@"name"];
}
大家可以尝试运行下代码,看一下效果。
解决方案
为了解决这个问题,在网上查找了很多资料,有的大神的思路是建立一个delegate,观察者和被观察者通过delegate间接建立联系,由于没有demo源码,我这边也不是太理解,而且觉得这种方案比较繁琐,后来考虑建立一个哈希表,用来保存,观察者,keyPath的信息,如果哈希表里已经有了相关的观察者,keyPath信息,那么继续添加观察者的话,就不载进行添加,同样移除观察的时候,也现在哈希表中进行查找,如果存在观察者,keypath信息,那么移除,如果没有的话就不执行相关的移除操作。要实现这样的思路就需要用到methodSwizzle来进行方法交换。我这通过写了一个NSObject的cagegory来进行方法交换。示例代码如下:
#pragma mark --- KVOCrashProtect
- (void)setKVOHashTable:(NSHashTable *)KVOHasTable{
objc_setAssociatedObject(self, &KVOHashTableKey, KVOHasTable, OBJC_ASSOCIATION_RETAIN);
}
- (NSHashTable *)KVOHashTable{
return objc_getAssociatedObject(self, &KVOHashTableKey);
}
- (void)JKCrashProtectaddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
if ([observer isKindOfClass:[UIViewController class]]) {
@synchronized (self) {
NSInteger kvoHash = [self _JKCrashProtectHash:observer :keyPath];
if (!self.KVOHashTable) {
self.KVOHashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsStrongMemory];
}
if (![self.KVOHashTable containsObject:@(kvoHash)]) {
[self.KVOHashTable addObject:@(kvoHash)];
[self JKCrashProtectaddObserver:observer forKeyPath:keyPath options:options context:context];
}
}
}else{
[self JKCrashProtectaddObserver:observer forKeyPath:keyPath options:options context:context];
}
}
当然了,对于KVO造成crash的原因,我这边可能分析的不够全面,欢迎大家多多指教,如果大家发现在使用KVO的时候还有别的原因造成crash欢迎给我留言啊,大家一块处理。JKCrashProtect源码
另外最近在完善JKCrashProtect的时候,觉得如果想要很好的解决或者避免Crash产生,那么我们需要对产生crash的原因,进行提前了解、分析,这样即使遇到了crash,我们也不会慌张。