博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS kvo 防止重复添加或者删除监听,实现safe kvo
阅读量:6826 次
发布时间:2019-06-26

本文共 3992 字,大约阅读时间需要 13 分钟。

  hot3.png

    在使用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
*)change context:(void *)context {}

这个方法多次。

2.某个属性根本没有添加observer,但是却在dealloc中移除了这个属性的观察者,或者是重复移除。这种情况会造成app 的闪退。

解决的方案是:创建一个NSObject的分类,运用iOS的运行时特性在运行时交换 addObserver 和 removeObserver的方法实现 ,通过读取 self.observationInfo 内容的值判断有误监听的属性值来进行添加和删除的操作 。self.observationInfo 是一个 void * 类型,下面是打印 self.observationInfo 私有属性 _observances 的值

(lldb) po [info valueForKey:@"_observances"]__NSArrayI 0x604000032f00( 
Context: 0x0, Property: 0x604000051af0>,
Context: 0x0, Property: 0x604000051c40>

发现里面存储的使我们已经添加的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 的key if ([keypath isEqualToString:kvoKey]) { hasKey = YES; break; } } return hasKey;}@end

把上面两个文件拖入到项目中就可以解决kvo重复添加或者删除observer的问题了 。

以上内容为个人总结,如果问题或疑问请留言联系。

转载于:https://my.oschina.net/zhxx/blog/1581097

你可能感兴趣的文章
Linux 搭建Open***服务器
查看>>
基于Cloudera Manager 5和CDH5(版本5.3.3)的Hadoop集群安装
查看>>
ES6 HttpApplication Middleware
查看>>
究竟有多少linux内核命令行参数
查看>>
PIX/ASA基于IP的限速详解实验配置
查看>>
N点虚拟主机管理系统 致命漏洞通杀所有版本(0day)
查看>>
2011年上半年网络工程师考试上午试卷与参考答案(2)
查看>>
某人收集的MSSQL扩展存储过程恢复方法—非常全面
查看>>
我的友情链接
查看>>
图片在线压解地址
查看>>
我的友情链接
查看>>
jvm和tomcat 版本不匹配,报java.lang.UnsupportedClassVersionError
查看>>
xhtml+css基础知识1
查看>>
Spring4学习笔记-声明式事务(基于配置文件的方式)
查看>>
Entity Framework 4 in Action读书笔记——第五章:域模型映射(Domain model mapping)(一)...
查看>>
kickstart+pxe无人值守安装linux
查看>>
java JNI数据类型对照表
查看>>
thinkphp-FOREACH标签2
查看>>
bootstrap-列表组
查看>>
浅谈javascript事件委托
查看>>