为什么80%的码农都做不了架构师?>>>
在使用KVO的时候添加观察者我们是这样做的:
// 监听btn 的selected属性改变
[self addObserver:self forKeyPath:@"selected" options:NSKeyValueObservingOptionNew context:nil];
需要特别注意的是:在dellaoc 的时候我们需要将监听的observer移除:
- (void)dealloc {[self removeObserver:self forKeyPath:@"selected"];
}
上面是在正常不过的操作了,但是我们在实际的项目可能会遇到
1.重复添加observer这种情况属性值的改变会回调:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:
(NSDictionary
这个方法多次。
2.某个属性根本没有添加observer,但是却在dealloc中移除了这个属性的观察者,或者是重复移除。这种情况会造成app 的闪退。
解决的方案是:创建一个NSObject的分类,运用iOS的运行时特性在运行时交换 addObserver 和 removeObserver的方法实现 ,通过读取 self.observationInfo 内容的值判断有误监听的属性值来进行添加和删除的操作 。self.observationInfo 是一个 void * 类型,下面是打印 self.observationInfo 私有属性 _observances 的值
(lldb) po [info valueForKey:@"_observances"]
__NSArrayI 0x604000032f00
(
发现里面存储的使我们已经添加的observers 集合。 每个NSKeyValueObservcance对面里面又存储了Observer的相关信息。 如下图:
通过获取 _keyPath的值,可以判断是不是已经添加或者存在该 observer,根据该值决定是否在我们自己的addObserver和removeObser方法中添加或删除observser 。
具体的实现如下:
.h文件
@interface NSObject (SafeKVO)/*!@method@abstract 移除所有观察的keypath*/
- (void)removeAllObserverdKeyPath;@end
.m文件
#import "NSObject+SafeKVO.h"
#import
@implementation NSObject (SafeKVO)+ (void)load {Method originAddM = class_getInstanceMethod([self class], @selector(addObserver:forKeyPath:options:context:));Method swizzAddM = class_getInstanceMethod([self class], @selector(swizz_addObserver:forKeyPath:options:context:));Method originRemoveM = class_getInstanceMethod([self class], @selector(removeObserver:forKeyPath:context:));Method swizzRemoveM = class_getInstanceMethod([self class], @selector(swizz_removeObserver:forKeyPath:context:));IMP originAddMIMP = class_getMethodImplementation([self class], @selector(addObserver:forKeyPath:options:context:));IMP originRemoveIMP = class_getMethodImplementation([self class], @selector(removeObserver:name:object:));BOOL hasAddM = class_addMethod([self class], @selector(addObserver:forKeyPath:options:context:), originAddMIMP, method_getTypeEncoding(originAddM));BOOL hasRemoveM = class_addMethod([self class], @selector(removeObserver:forKeyPath:context:), originRemoveIMP, method_getTypeEncoding(originRemoveM));// excute once static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{if (!hasAddM) {method_exchangeImplementations(originAddM, swizzAddM);}if (!hasRemoveM) {method_exchangeImplementations(originRemoveM, swizzRemoveM);}});
}- (void)swizz_addObserver:(nonnull NSObject *)observer forKeyPath:(nonnull NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {if (![self hasKey:keyPath]) {// 调用系统的添加observer 方法[self swizz_addObserver:observer forKeyPath:keyPath options:options context:context];}
}
- (void)swizz_removeObserver:(nonnull NSObject *)observer forKeyPath:(nonnull NSString *)keyPath context:(nullable void *)context {if ([self hasKey:keyPath]) {[self swizz_removeObserver:observer forKeyPath:keyPath context:context];}
}- (void)removeAllObserverdKeyPath {id info = self.observationInfo;NSArray *arr = [info valueForKeyPath:@"_observances._property._keyPath"];for (NSString *keyPath in arr) {// TODO context 需要考虑值不为nil的时候[self removeObserver:self forKeyPath:keyPath context:nil];}
}- (BOOL)hasKey:(NSString *)kvoKey {BOOL hasKey = NO;id info = self.observationInfo;NSArray *arr = [info valueForKeyPath:@"_observances._property._keyPath"];for (id keypath in arr) {// 存在kvo 的keyif ([keypath isEqualToString:kvoKey]) {hasKey = YES;break;}}return hasKey;
}@end
把上面两个文件拖入到项目中就可以解决kvo重复添加或者删除observer的问题了 。
以上内容为个人总结,如果问题或疑问请留言联系。