KVO Crash,通常是KVO的被观察者dealloc时仍然注册着KVO导致的crash,添加KVO重复添加观察者或重复移除观察者引起的。
一个被观察的对象上有若干个观察者,每个观察者又有若干条keypath。如果观察者和keypathx的数量一多,很容易不清楚被观察的对象整个KVO关系,导致被观察者在dealloc的时候,仍然残存着一些关系没有被注销,同时还会导致KVO注册者和移除观察者不匹配的情况发生。尤其是多线程的情况下,导致KVO重复添加观察者或者移除观察者的情况,这种类似的情况通常发生的比较隐蔽,很难从代码的层面上排查。
解决方法:
可以让观察对象持有一个KVO的delegate,所有和KVO相关的操作均通过delegate来进行管理,delegate通过建立一张MAP表来维护KVO的整个关系,这样做的好处有2个:
1:如果出现KVO重复添加观察或者移除观察者(KVO注册者不匹配的)情况,delegate,可以直接阻止这些非正常的操作。
2:被观察对象dealloc之前,可以通过delegate自动将与自己有关的KVO关系都注销掉,避免了KVO的被观察者dealloc时仍然注册着KVO导致的crash
具体方式:
1、自定义一个继承自NSObject的代理类,并通过Catagory将这个代理类作为NSObject的属性进行关联
#import
#import "XZKVOProxy.h"
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (KVOCrash)
@property (nonatomic, strong) XZKVOProxy * _Nullable KVOProxy; // 自定义的kvo关系的代理
@end
NS_ASSUME_NONNULL_END
#import "NSObject+KVOCrash.h"
#import "XZKVOProxy.h"
#import
#pragma mark - NSObject + KVOCrash
static void *NSObjectKVOProxyKey = &NSObjectKVOProxyKey;
@implementation NSObject (KVOCrash)
- (XZKVOProxy *)KVOProxy {
id proxy = objc_getAssociatedObject(self, NSObjectKVOProxyKey);
if (nil == proxy) {
proxy = [XZKVOProxy kvoProxyWithObserver:self];
self.KVOProxy = proxy;
}
return proxy;
}
- (void)setKVOProxy:(XZKVOProxy *)proxy
{
objc_setAssociatedObject(self, NSObjectKVOProxyKey, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
2、在自定义代理类中建立一个map来维护KVO整个关系
#import
typedef void (^XZKVONitificationBlock)(id _Nullable observer, id _Nullable object, NSDictionary * _Nullable change);
/**
KVO配置类
用于存储KVO里面的相关设置参数
*/
@interface XZKVOInfo : NSObject
//- (instancetype _Nullable)initWithObserver:(id _Nonnull)object keyPath:(NSString * _Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(void * _Nullable)context block:(XZKVONitificationBlock _Nonnull )block;
@end
NS_ASSUME_NONNULL_BEGIN
/**
KVO管理类
用于管理object添加和移除的消息,(通过Map进行KVO之间的关系)(字典应该也可以)
*/
@interface XZKVOProxy : NSObject
@property (nullable, nonatomic, weak, readonly) id observer;
+ (instancetype)kvoProxyWithObserver:(nullable id)observer;
- (void)xz_observer:(id _Nullable)object keyPath:(NSString * _Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(void * _Nullable)context block:(XZKVONitificationBlock)block;
- (void)xz_unobserver:(id _Nullable)object keyPath:(NSString * _Nullable)keyPath;
- (void)xz_unobserver:(id _Nullable)object;
- (void)xz_unobserverAll;
@end
NS_ASSUME_NONNULL_END
#import "XZKVOProxy.h"
#import
@interface XZKVOInfo ()
{
@public
__weak id _object; // 观察对象
NSString *_keyPath;
NSKeyValueObservingOptions _options;
SEL _action;
void *_context;
XZKVONitificationBlock _block;
}
@end
@implementation XZKVOInfo
- (instancetype _Nullable)initWithObserver:(id _Nonnull)object
keyPath:(NSString * _Nullable)keyPath
options:(NSKeyValueObservingOptions)options
context:(void * _Nullable)context {
return [self initWithObserver:object keyPath:keyPath options:options block:NULL action:NULL context:context];
}
- (instancetype _Nullable)initWithObserver:(id _Nonnull)object
keyPath:(NSString * _Nullable)keyPath
options:(NSKeyValueObservingOptions)options
context:(void * _Nullable)context
block:(XZKVONitificationBlock)block {
return [self initWithObserver:object keyPath:keyPath options:options block:block action:NULL context:context];
}
- (instancetype _Nullable)initWithObserver:(id _Nonnull)object
keyPath:(NSString * _Nullable)keyPath
options:(NSKeyValueObservingOptions)options
block:(_Nullable XZKVONitificationBlock)block
action:(_Nullable SEL)action
context:(void * _Nullable)context {
if (self = [super init]) {
_object = object;
_block = block;
_keyPath = [keyPath copy];
_options = options;
_action = action;
_context = context;
}
return self;
}
@end
/**
此类用来管理混乱的KVO关系
让被观察对象持有一个KVO的delegate,所有和KVO相关的操作均通过delegate来进行管理,delegate通过建立一张map来维护KVO整个关系
好处:
不会crash如果出现KVO重复添加观察者或重复移除观察者(KVO注册观察者与移除观察者不匹配)的情况,delegate可以 1.直接阻止这些非正常的操作。
crash 2.被观察对象dealloc之前,可以通过delegate自动将与自己有关的KVO关系都注销掉,避免了KVO的被观察者dealloc时仍然注册着KVO导致的crash。
:
重复添加观察者不会crash,即不会走@catch
多次添加对同一个属性观察的观察者,系统方法内部会强应用这个观察者,同理即可remove该观察者同样次数。
*/
@interface XZKVOProxy ()
{
pthread_mutex_t _mutex;
NSMapTable *> *_objectInfoMap;///< map来维护KVO整个关系
}
@end
@implementation XZKVOProxy
+ (instancetype)kvoProxyWithObserver:(nullable id)observer {
return [[self alloc] initWithObserver:observer];
}
- (instancetype)initWithObserver:(nullable id)observer {
if (self = [super init]) {
_observer = observer;
_objectInfoMap = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality capacity:0];
}
return self;
}
/**
加锁、解锁
*/
- (void)lock {
pthread_mutex_lock(&_mutex);
}
- (void)unlock {
pthread_mutex_unlock(&_mutex);
}
/**
添加、删除 观察者
*/
- (void)xz_observer:(id _Nullable)object keyPath:(NSString * _Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(void * _Nullable)context block:(XZKVONitificationBlock)block {
// 断言
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// 将观察的信息转成info对象
// self即kvoProxy是观察者;object是被观察者
XZKVOInfo *info = [[XZKVOInfo alloc] initWithObserver:self keyPath:keyPath options:options context:context block:block];
if (info) {
// 将info以key-value的形式存储到map中。key是被观察对象;value是观察信息的集合。
// 加锁
[self lock];
NSMutableSet *infos = [_objectInfoMap objectForKey:object];
BOOL _isExisting = NO;
for (XZKVOInfo *existingInfo in infos) {
if ([existingInfo->_keyPath isEqualToString:info->_keyPath]) {
// 观察者已存在
_isExisting = YES;
break;
}
}
if (_isExisting == YES) {
// 解锁
[self unlock];
return;
}
// // check for info existence
// XZKVOInfo *existingInfo = [infos member:info];
// if (nil != existingInfo) {
// // observation info already exists; do not observe it again
//
// // 解锁
// [self unlock];
// return;
// }
// 不存在
if (infos == nil) {
// 创建set,并将set添加进Map里
infos = [NSMutableSet set];
[_objectInfoMap setObject:infos forKey:object];
}
// 将要添加的KVOInfo添加进set里面
[infos addObject:info];
// 解锁
[self unlock];
// 将 kvoProxy 作为观察者;添加观察者
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:info->_context];
}
}
- (void)xz_unobserver:(id _Nullable)object keyPath:(NSString * _Nullable)keyPath {
// 将观察的信息转成info对象
// self即kvoProxy是观察者;object是被观察者
XZKVOInfo *info = [[XZKVOInfo alloc] initWithObserver:self keyPath:keyPath options:0 context:nil];
// 加锁
[self lock];
// 从map中获取object对应的KVOInfo集合
NSMutableSet *infos = [_objectInfoMap objectForKey:object];
BOOL _isExisting = NO;
for (XZKVOInfo *existingInfo in infos) {
if ([existingInfo->_keyPath isEqualToString:info->_keyPath]) {
// 观察者已存在
_isExisting = YES;
info = existingInfo;
break;
}
}
if (_isExisting == YES) {
// 存在
[infos removeObject:info];
// remove no longer used infos
if (0 == infos.count) {
[_objectInfoMap removeObjectForKey:object];
}
// 解锁
[self unlock];
// 移除观察者
[object removeObserver:self forKeyPath:info->_keyPath context:info->_context];
} else {
// 解锁
[self unlock];
}
// XZKVOInfo *registeredInfo = [infos member:info];
//
// if (nil != registeredInfo) {
// [infos removeObject:registeredInfo];
//
// // remove no longer used infos
// if (0 == infos.count) {
// [_objectInfoMap removeObjectForKey:object];
// }
//
// // 解锁
// [self unlock];
//
//
// // 移除观察者
// [object removeObserver:self forKeyPath:registeredInfo->_keyPath context:registeredInfo->_context];
// } else {
// // 解锁
// [self unlock];
// }
}
- (void)xz_unobserver:(id _Nullable)object {
// 加锁
[self lock];
// 从map中获取object对应的KVOInfo集合
NSMutableSet *infos = [_objectInfoMap objectForKey:object];
[_objectInfoMap removeObjectForKey:object];
// 解锁
[self unlock];
// 批量移除观察者
for (XZKVOInfo *info in infos) {
// 移除观察者
[object removeObserver:self forKeyPath:info->_keyPath context:info->_context];
}
}
- (void)xz_unobserverAll {
if (_objectInfoMap) {
// 加锁
[self lock];
// copy一份map,防止删除数据异常冲突
NSMapTable *objectInfoMaps = [_objectInfoMap copy];
[_objectInfoMap removeAllObjects];
// 解锁
[self unlock];
// 移除全部观察者
for (id object in objectInfoMaps) {
NSSet *infos = [objectInfoMaps objectForKey:object];
if (!infos || infos.count == 0) {
continue;
}
for (XZKVOInfo *info in infos) {
[object removeObserver:self forKeyPath:info->_keyPath context:info->_context];
}
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
// NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
NSLog(@"%@",keyPath);
NSLog(@"%@",object);
NSLog(@"%@",change);
NSLog(@"%@",context);
// 从map中获取object对应的KVOInfo集合
NSMutableSet *infos = [_objectInfoMap objectForKey:object];
BOOL _isExisting = NO;
XZKVOInfo *info;
for (XZKVOInfo *existingInfo in infos) {
if ([existingInfo->_keyPath isEqualToString:keyPath]) {
// 观察者已存在
_isExisting = YES;
info = existingInfo;
break;
}
}
if (_isExisting == YES && info) {
XZKVOProxy *proxy = info->_object;
id observer = proxy.observer;
XZKVONitificationBlock block = info->_block;
if (block) {
block(observer, object, change);
}
}
}
- (void)dealloc {
// 移除所有观察者
[self xz_unobserverAll];
// 销毁mutex
pthread_mutex_destroy(&_mutex);
}
@end
ps:具体参考了第三方组件FBKVOController的思路,如果使用的话,可以使用这些更加成熟的第三方组件。
ps:在进行总结中,特意进行了这两个操作: KVO的被观察者dealloc时仍然注册着KVO导致的crash,添加KVO重复添加观察者或重复移除观察者。发现应用并没有崩溃,不知是苹果修复了还是我操作的失误。有兴趣的可以自己实验下