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