文档章节

iOS kvo 防止重复添加或者删除监听,实现safe kvo

zh_iOS
 zh_iOS
发布于 2017/11/28 17:06
字数 747
阅读 534
收藏 0

    在使用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<NSKeyValueChangeKey,id> *)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
(
 <NSKeyValueObservance 0x604000051b80: Observer: 0x7ffd82c0e9b0, Key path: selected, 
Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x604000051af0>,
 <NSKeyValueObservance 0x604000051bb0: Observer: 0x7ffd82c0e9b0, Key path: highlighted,
 Options: <New: YES, Old: YES, Prior: NO> 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 <objc/runtime.h>
@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的问题了 。

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

© 著作权归作者所有

共有 人打赏支持
zh_iOS
粉丝 28
博文 73
码字总数 34061
作品 0
石景山
程序员
私信 提问
iOS源码补完计划--AFNetworking(二)

目录 前言 AFNetworkReachabilityManager.h AFNetworkReachabilityManager.m API注释Demo 参考资料 前言 AFNetworking源码第二篇 主要看了看AFNetworkReachabilityManager的内容 作为一个辅助...

kirito_song
2018/05/16
0
0
iOS底层原理总结 - 探寻KVO本质

对小码哥底层班视频学习的总结与记录。面试题部分,通过对面试题的分析探索问题的本质内容。 问题 iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?) 如何手动触发KVO 首先需要了解K...

xx_cc
2018/04/21
0
0
iOS高仿QQ侧滑控件、下载框架、动画效果、扫一扫、颜色变化、K线图等源码

iOS精选源码 仿京东"加入购物车"转场动画(http://www.code4app.com/thread-28162-1-1.html) ColorTool(颜色转换)(http://www.code4app.com/thread-29256-1-1.html) Swift 专业版K线(http://w......

sunnyaigd
2018/04/17
0
0
文檔翻譯:NSOperation Class Reference

NSOperation Class Reference NSOperation 類參考 http://db.tt/A1xLPVXq。(排版較佳) 原文網址:http://developer.apple.com/documentation/Cocoa/Reference/NSOperation_class/Referenc......

KSHime
2012/09/18
0
0
iOS源码补完计划--AFNetworking 3.1.0源码研读

参拜一下AFNetworking的源码。 第四篇源码、暂时来看也是iOS方向的最后一篇、撸完准备趁着热乎撸一撸网络协议。 目录 准备工作 功能模块 AFURLSessionManager/AFHTTPSessionManager AFNetwo...

kirito_song
2018/05/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

【C++】智能指针简述(四):shared_ptr

  在开始本文内容之前,我们再来总结一下,前文内容:   1.智能指针采用RAII机制,在构造对象时进行资源的初始化,析构对象时进行资源的清理及汕尾.   2.auto_ptr防止拷贝后析构释放同一块内...

shzwork
29分钟前
1
0
作为Java程序员这些技术都不会,拿什么去涨薪跳槽?

引言 当下,正面临着近几年来的最严重的互联网寒冬,听得最多的一句话便是:相见于江湖~,缩减HC、裁员不绝于耳,大家都是人心惶惶,年前如此,年后想必肯定又是一场更为惨烈的江湖厮杀。但博...

别打我会飞
52分钟前
2
0
springboot开发之定时器quartz 定时任务调度(压缩版,抽取quartz的单个任务表实现)

前言 老了, 记不住了, 好记性不如烂笔头; 没想到曾经过目不忘的我, 也有这么一天, 岁月蹉跎,学习一天不如一天 难受 Quartz可以用来做什么? Quartz是一个任务调度框架。比如你遇到这样的问题...

尾生
57分钟前
11
0
技术经理平时都干啥?

「技术主管」是开发团队中的某位程序员需要对一起创建系统的整个开发团队负责时所承担的角色。通常他既要对最终交付的软件系统负责,另外也会像一个程序员一样去开发实现系统。 一个技术主管...

春哥大魔王的博客
今天
7
0
java工作流引擎Jflow流程事件和流程节点事件设置

流程实例的引入和设置 关键词: 开源工作流引擎 Java工作流开发 .net开源工作流引擎 流程事件 工作流节点事件 应用场景: 在一些复杂的业务逻辑流程中需要在某个节点或者是流程结束后做一些业...

ccflow周朋
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部