美文网首页
iOS崩溃处理机制:KVO Crash

iOS崩溃处理机制:KVO Crash

作者: 光之盐汽水 | 来源:发表于2020-10-14 19:22 被阅读0次

    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 <Foundation/Foundation.h>
    #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 <objc/runtime.h>
    
    
    #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 <Foundation/Foundation.h>
    
    
    typedef void (^XZKVONitificationBlock)(id _Nullable observer, id _Nullable object, NSDictionary<NSString *, id> * _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 <pthread/pthread.h>
    
    
    @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<id, NSMutableSet<XZKVOInfo *> *> *_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<NSKeyValueChangeKey,id> *)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

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