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

2021-06-07  本文已影响0人  butterflyer

是否经常在项目中遇到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,进行之前,进行判断是否已经存在或者是已经移除,来避免问题。
其实我比较推荐第二种解决方案。因为第一种应该会有一些特殊情况不能完全覆盖到。

上一篇下一篇

猜你喜欢

热点阅读