iOS崩溃处理机制:KVO Crash

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重复添加观察者或重复移除观察者。发现应用并没有崩溃,不知是苹果修复了还是我操作的失误。有兴趣的可以自己实验下

你可能感兴趣的:(iOS崩溃处理机制:KVO Crash)