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)

用户喜欢...

电源管理集成电路(PMIC)如何减少这些挑战的影响

在设计具有现场可编程门阵列(FPGA)的系统时,系统设计人员可以获得三个好处:可重编程性,性能可扩展性和快速上市时间。但是,设计师也必须克服挑战。在这篇文章中,我将讨论电源管...


为了提高效率 - 如何将双向功率流集成到UPS设计中(第2部分)

在本系列的第1部分中,我讨论了如何将双向功率流集成到不间断电源(UPS)设计中。在第二部分中,我将更详细地介绍用于UPS和电池备份应用的2kW,48V至400V, 93%效率,隔离双向DC / DC转换器参...


为了提高效率 - 如何将双向功率流集成到UPS设计中(第1部分)

随着对更紧凑,更小和更高效的电力系统的日益重视,对双向转换器的兴趣日益增加。能够双向功率流的双向DC / DC转换器可以将传统上用于电池充电和备份操作所需的两个DC / DC转换器组合成一...


如何为高性能、低侧电流传感设计电路板?

在我以前的 博客帖子 我讨论了低侧电流传感如何帮助控制电机,然后给出了设计低侧电流传感电路的三个步骤,以适用于成本敏感的应用。在这篇文章中,我将讨论如何使用正确的印刷电路板...


使用光学传感器解决传感挑战

能够适应不断变化的应用要求的测量系统是首选,因为不断增长的成本压力需要较长的系统寿命和功能灵活性。开发此类系统的最有效方法是使用像PXI这样的软件定义的模块化架构。可以混合和...


如何设计-130-dB超低失真数据采集系统

数据采集​​系统(DAS)是工业,医疗和电信应用的标准测量和分析仪器,包括医学成像,音频和振动分析以及模拟和数字调制系统的测试。在大多数情况下,采集和数字化信号通过快速傅里叶...


自主汽车传感器:处理器算法如何获得输入

尽管鉴于最近特斯拉级别S的死亡,有关自主汽车消亡的非技术性媒体报道,我想在本文中介绍传感器电子设备,结合更好和更精细的软件算法,最终将实现在未来十年内,一辆安全,完全自主...


如何设计电感式触摸金属按钮面板

人机界面(HMI)正在经历地震变化,现在有一种新方法可用于设计按钮面板。在创建金属触摸(ToM)按钮时,电感式传感技术正成为首选方法。主要原因是它可以降低成本,同时更加可靠,因为...


使用DLP技术创建高分辨率自适应大灯

传统上,典型的汽车前照灯光束仅照亮车辆前方的物体,以提高驾驶员在低光照和恶劣天气条件下的可视性。低光束在车辆前方短距离照亮道路,而远光灯具有更长的范围和更宽的角度。这种...


使用原型套件简化能量收集设计

在使用设计之前,根据其环境条件,能量收集电路能够提供多少功率可能并不明显。 这会影响能量收集源和能量存储所需的尺寸和质量。 为了使设计进一步复杂化,诸如能量存储泄漏,稳压器...