close
当前位置: 物联网在线 > 技术文库 > ios >

如何优雅地使用 KVO

KVO 作为 iOS 中一种强大并且有效的机制,为 iOS 开发者们提供了很多的便利;我们可以使用 KVO 来检测对象属性的变化、快速做出响应,这能够为我们在开发强交互、响应式应用以及实现视图和模型的双向绑定时提供大量的帮助。

但是在大多数情况下,除非遇到不用 KVO 无法解决的问题,笔者都会尽量避免它的使用,这并不是因为 KVO 有性能问题或者使用场景不多,总重要的原因是 KVO 的使用是在是太 ** 麻烦 了。

如何优雅地使用 KVO

使用 KVO 时,既需要进行 注册成为某个对象属性的观察者 ,还要在合适的时间点将自己 移除 ,再加上需要 覆写一个又臭又长的方法 ,并在方法里 判断这次是不是自己要观测的属性发生了变化 ,每次想用 KVO 解决一些问题的时候,作者的第一反应就是头疼,这篇文章会为各位为 KVO 所苦的开发者提供一种更优雅的解决方案。

使用 KVO

不过在介绍如何优雅地使用 KVO 之前,我们先来回忆一下,在通常情况下,我们是如何使用 KVO 进行键值观测的。

首先,我们有一个 Fizz 类,其中包含一个 number 属性,它在初始化时会自动被赋值为 @0 :

// Fizz.h @interface Fizz : NSObject @property (nonatomic, strong) NSNumber *number; @end // Fizz.m @implementation Fizz - (instancetype)init { if (self = [super init]) { _number = @0; } return self; } @end

我们想在 Fizz 对象中的 number 对象发生改变时获得通知得到 的和 的值,这时我们就要祭出 -addObserver:forKeyPath:options:context 方法来监控 number 属性的变化:

Fizz *fizz = [[Fizz alloc] init]; [fizz addObserver:self forKeyPath:@"number" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; fizz.number = @2;

在将当前对象 self 注册成为 fizz 的观察者之后,我们需要在当前对象中覆写 -observeValueForKeyPath:ofObject:change:context: 方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"number"]) { NSLog(@"%@", change); } }

在大多数情况下我们只需要对比 keyPath 的值,就可以知道我们到底监控的是哪个对象,但是在更复杂的业务场景下,使用 context 上下文以及其它辅助手段才能够帮助我们更加精准地确定被观测的对象。

但是当上述代码运行时,虽然可以成功打印出 change 字典,但是却会发生崩溃,你会在控制台中看到下面的内容:

2017-02-26 23:44:19.666 KVOTest[15888:513229] { kind = 1; new = 2; old = 0; } 2017-02-26 23:44:19.720 KVOTest[15888:513229] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x60800001dd20 of class Fizz was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x60800003d320> ( <NSKeyValueObservance 0x608000057310: Observer: 0x7fa098f07590, Key path: number, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x608000057400> )'

这是因为 fizz 对象没有被其它对象引用,在脱离 viewDidLoad 作用于之后就被回收了,然而在 -dealloc 时,并没有移除观察者,所以会造成崩溃。

我们可以使用下面的代码来验证上面的结论是否正确:

// Fizz.h @interface Fizz : NSObject @property (nonatomic, strong) NSNumber *number; @property (nonatomic, weak) NSObject *observer; @end // Fizz.m @implementation Fizz - (instancetype)init { if (self = [super init]) { _number = @0; } return self; } - (void)dealloc { [self removeObserver:self.observer forKeyPath:@"number"]; } @end

在 Fizz 类的接口中添加一个 observer 弱引用来持有对象的观察者,并在对象 -dealloc 时将它移除,重新运行这段代码,就不会发生崩溃了。

如何优雅地使用 KVO

由于没有移除观察者导致崩溃使用 KVO 时经常会遇到的问题之一,解决办法其实有很多,我们在这里简单介绍一个,使用当前对象持有被观测的对象,并在当前对象 -dealloc 时,移除观察者:

- (void)viewDidLoad { [super viewDidLoad]; self.fizz = [[Fizz alloc] init]; [self.fizz addObserver:self forKeyPath:@"number" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; self.fizz.number = @2; } - (void)dealloc { [self.fizz removeObserver:self forKeyPath:@"number"]; }

这也是我们经常使用来避免崩溃的办法,但是在笔者看来也是非常的不优雅,除了上述的崩溃问题,使用 KVO 的过程也非常的别扭和痛苦:

需要手动 移除观察者 ,且移除观察者的 时机必须合适

注册观察者的代码和事件发生处的代码上下文不同, 传递上下文 是通过 void * 指针;

需要覆写 -observeValueForKeyPath:ofObject:change:context: 方法,比较麻烦;

在复杂的业务逻辑中,准确判断被观察者相对比较麻烦,有多个被观测的对象和属性时,需要在方法中写大量的 if 进行判断;

虽然上述几个问题并不影响 KVO 的使用,不过这也足够成为笔者尽量不使用 KVO 的理由了。

优雅地使用 KVO

如何优雅地解决上一节提出的几个问题呢?我们在这里只需要使用 Facebook 开源的 KVOController 框架就可以优雅地解决这些问题了。


(责任编辑:ioter)

用户喜欢...

在 Airbnb 使用机器学习预测房源的价格

作者:Robert Chang 位于希腊爱琴海伊莫洛维里的一个 Airbnb 民宿的美好风景 简介 数据产品一直是 Airbnb 服务的重要组成...


万物互联时代,看恩智浦如何发力?

我们正生活在一个被物联网逐渐影响和改变的宇宙中,而各种新兴的万物互联应用,如同浩翰夜空下的闪烁星辰,熠...


5G:毫米波多天线无法采用线连测试如何解决?

移动通信和WLAN正向5G和802.11ax/ay演进,以便满足人们日益增长的海量数据传输需求。5G中新增毫米波频段,除了能够提...


如何在IAR中配置CRC参数

STM32全系列产品都具有CRC外设,对CRC的计算提供硬件支持,为应用程序节省了代码空间。CRC校验值可以用于数据传输中的数据正确性的验证,也可用于数据存储时的完整性检查。在IEC60335中,...


如何使用可编程逻辑为按钮输入消抖

可编程逻辑具有传统分立 IC 无法提供的灵活性。 借助现成的开发工具,可轻松使用现场可编程门阵列和复杂可编程逻辑器件创建应用特定型功能。 按钮开关输入消抖便是此类功能的一个例子...


万物互联时代,看恩智浦如何发力?

我们正生活在一个被物联网逐渐影响和改变的宇宙中,而各种新兴的万物互联应用,如同浩翰夜空下的闪烁星辰,熠...


使用 LoRa 进行低速率、长距离物联网应用开发

设计人员可以使用各种各样的无线技术将产品连接到物联网 (IoT)。每种技术适用于不同的应用,需要设计人员仔细考虑作用距离和数据速率、成本、功耗、体积和外形等因素。 本文将介绍 LoR...


探索如何应用快如闪电的内部补偿式ACM拓扑

具有内部补偿的高级电流模式(ACM)是TI开发的一款新型控制拓扑,可以支持真定频调制并与内部补偿同步。从根本上来...


一次下电和二次下电到底有什么区别? 如何操作?

所谓的一次下电、二次下电是针对开关电源(电源柜)说的。为什么要设置二次下电电压呢?为了保护电池组不会出...


一块PCB板上如何安置RF电路和数字电路这两尊大神?

单片射频器件大大方便了一定范围内无线通信领域的应用,采用合适的微控制器和天线并结合此收发器件即可构成完整的无线通信链路。它们可以集成在一块很小的电路板上,应用于无线数字...