美文网首页
『ios』kvo中安全的移除监听

『ios』kvo中安全的移除监听

作者: butterflyer | 来源:发表于2021-06-07 20:39 被阅读0次

    是否经常在项目中遇到kvo移除崩溃的错误?
    其实我们可以用try catch来解决这个问题。
    今天看到了二种比较优雅的解决办法。

    + (void)load
    {
        [self switchMethod];
    }
    
    + (void)switchMethod
    {
        SEL removeSel = @selector(removeObserver:forKeyPath:);
        SEL myRemoveSel = @selector(removeSafe:forKeyPath:);
        SEL addSel = @selector(addObserver:forKeyPath:options:context:);
        SEL myaddSel = @selector(addSafe:forKeyPath:options:context:);
        
        Method systemRemoveMethod = class_getClassMethod([self class],removeSel);
        Method DasenRemoveMethod = class_getClassMethod([self class], myRemoveSel);
        Method systemAddMethod = class_getClassMethod([self class],addSel);
        Method DasenAddMethod = class_getClassMethod([self class], myaddSel);
        
        method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
        method_exchangeImplementations(systemAddMethod, DasenAddMethod);
    }
    
    // 交换后的方法
    - (void)removeSafe:(NSObject *)observer forKeyPath:(NSString *)keyPath
    {
        if ([self observerKeyPath:keyPath observer:observer]) {
            [self removeSafe:observer forKeyPath:keyPath];
        }
    }
    
    // 交换后的方法
    - (void)addSafe:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
    {
        if (![self observerKeyPath:keyPath observer:observer]) {
            [self addSafe:observer forKeyPath:keyPath options:options context:context];
        }
    }
    
    
    // 进行检索获取Key
    - (BOOL)observerKeyPath:(NSString *)key observer:(id )observer
    {
        id info = self.observationInfo;
        NSArray *array = [info valueForKey:@"_observances"];
        for (id objc in array) {
            id Properties = [objc valueForKeyPath:@"_property"];
            id newObserver = [objc valueForKeyPath:@"_observer"];
            
            NSString *keyPath = [Properties valueForKeyPath:@"_keyPath"];
            if ([key isEqualToString:keyPath] && [newObserver isEqual:observer]) {
                return YES;
            }
        }
        return NO;
    }
    
    

    我们可以利用

        id info = self.observationInfo;
        NSArray *array = [info valueForKey:@"_observances"];
    

    拿到当前类进行kvo监听到对象。如下图所示

    image.png

    然后通过对比监听到key和监听的对象是否相同。

     id Properties = [objc valueForKeyPath:@"_property"];
            id newObserver = [objc valueForKeyPath:@"_observer"];
    
    image.png

    还有一种解决方案。

    @interface ObserverData : NSObject
    @property (nonatomic, strong)id objc;
    @property (nonatomic, copy)  NSString *keyPath;
    - (instancetype)initWithObjc:(id)objc key:(NSString *)key;
    
    @end
    @implementation ObserverData
    - (instancetype)initWithObjc:(id)objc key:(NSString *)key
    {
        if (self = [super init]) {
            self.objc = objc;
            self.keyPath = key;
        }
        return self;
    }
    @end
    
    #import "DSObserver.h"
    @implementation DSObserver
    + (instancetype)sharedDSObserver
    {
        static id objc;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            objc = [NSMutableArray array];
        });
        return objc;
    }
    @end
    
    
    #pragma mark - 第二种方案,利用私有属性
    // 交换后的方法
    - (void)removeSafe:(NSObject *)observer forKeyPath:(NSString *)keyPath
    {
        NSMutableArray *Observers = [DSObserver sharedDSObserver];
        ObserverData *userPathData = [self observerKeyPath:keyPath];
        // 如果有该key值那么进行删除
        if (userPathData) {
            [Observers removeObject:userPathData];
            [self removeSafe:observer forKeyPath:keyPath];
        }
        return;
    }
    
    // 交换后的方法
    - (void)addSafe:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
    {
        ObserverData *userPathData= [[ObserverData alloc]initWithObjc:self key:keyPath];
        NSMutableArray *Observers = [DSObserver sharedDSObserver];
    
        // 如果没有注册,那么才进行注册
        if (![self observerKeyPath:keyPath]) {
            [Observers addObject:userPathData];
            [self addSafe:observer forKeyPath:keyPath options:options context:context];
        }
    
    }
    //
    // 进行检索,判断是否已经存储了该Key值
    - (ObserverData *)observerKeyPath:(NSString *)keyPath
    {
        NSMutableArray *Observers = [DSObserver sharedDSObserver];
        for (ObserverData *data in Observers) {
            if ([data.objc isEqual:self] && [data.keyPath isEqualToString:keyPath]) {
                return data;
            }
        }
        return nil;
    }
    
    

    可以新建一个全局的管理kvo监听管理者数组,然后如果监听就add,移除就remove,进行之前,进行判断是否已经存在或者是已经移除,来避免问题。
    其实我比较推荐第二种解决方案。因为第一种应该会有一些特殊情况不能完全覆盖到。

    相关文章

      网友评论

          本文标题:『ios』kvo中安全的移除监听

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