【发布时间】:2013-02-14 13:42:51
【问题描述】:
我目前正在为 iOS 重写一个表单控制器。它是一个绑定到模型的自定义对象,并处理编辑表单字段、跳转到上一个/下一个字段、处理自定义键盘、验证数据...
第一个版本基于用于存储表单值的 plist,表单控制器本身保存所有数据。现在我想将存储(模型)与表单控制器分离,因此我决定使用 KVO。
为简单起见,我们假设我有一个设计用于编辑缺勤时间跨度的表单。所以它有两个字段:leaveDate 和 returnDate。
我的模型如下:
@interface Absence
@property (strong, nonatomic) NSDate *leaveDate;
@property (strong, nonatomic) NSDate *returnDate;
@property (readonly, nonatomic) BOOL isValid;
@end
我的表单控制器有一个属性model 指向这个对象。
当用户点击我的 XIB 中的“离开日期”文本字段时,表单控制器会根据我模型的 leaveDate 的当前值提交并显示一个日期选择器。当用户选择其他日期时,表单控制器会使用setValue:forKey: 更新其模型。
isValid 属性被声明为受到leaveDate 和returnDate 的影响(使用+keyPathsForValuesAffectingIsValid),并且表单控制器已注册以查看此属性的更改,以启用/禁用提交按钮苍蝇。
到目前为止,一切都像魅力一样。现在,对于扭曲的部分:
我希望我的表单控制器能够在模型打开时处理模型中的更改。示例:我在模型中有一条规则,即“缺勤必须至少持续 3 天”。当用户更改休假日期时,如果总时长不超过 3 天,则自动调整返回日期。
所以我的表单控制器还必须注册以监听所有属性的变化。问题是它既改变了属性,又监听了变化。
这样,当用户更改leaveTime 时,表单控制器会使用setValue:forKey: 更新模型,但会立即收到关于它刚刚所做的更改的KVO 通知。这是不必要的并且可能有害(我只是自己进行了更改,不需要告诉我我刚刚完成了)。
到目前为止,我发现的唯一方法是在设置新值之前取消注册,然后立即重新注册,如下所示:
[self.model removeObserver:self forKeyPath:self.currentField.key];
[self.model setValue:newValue forKey:self.currentField.key];
[self.model addObserver:self forKeyPath:self.currentField.key options:NSKeyValueObservingOptionNew context:nil];
它可以工作,但它很丑,而且性能方面我怀疑它是否很棒。
有人解释如何做得更好吗?
TL;DR
ControllerA是Model的注册KVO观察者。
ControllerB 更新 Model ==> ControllerA 收到 KVO 通知。没关系。
ControllerA 更新 Model ==> ControllerA 收到 KVO 通知。我不要这个。
【问题讨论】:
-
听到 KVO 通知您刚刚所做的更改究竟有什么危险?我认为该选项比删除然后重新添加自己作为 KVO 观察者更“不必要且可能有害”。
-
当我在更新模型之前做的最后一件事是
textField.text = @"something";时,我不想执行textField.text = @"something";。想象一下,这是一个成本更高的 UI 更新(重绘大控件、图像处理……),而且频繁发生(离散控件,如滑块)。 -
如果您可以确定接收通知实际上是一个幂等操作,那么多次接收相同的通知应该没有关系。
-
在某些情况下,即使您“提前”知道它需要更新,也可以选择依靠 KVO 通知来进行 UI 的实际更新。当你意识到它会发生时,不要真正更新你自己的 UI;只需更新模型并等待 KVO 通知导致 UI 更新。
-
这就是我刚刚在这里与我的同事讨论的内容。这是另一种选择,即使在文本字段的情况下它的行为对我来说仍然不清楚。
标签: objective-c model-view-controller key-value-observing