【问题标题】:Two-way KVO: controller updates model, which notifies controller双向KVO:控制器更新模型,通知控制器
【发布时间】:2013-02-14 13:42:51
【问题描述】:

我目前正在为 iOS 重写一个表单控制器。它是一个绑定到模型的自定义对象,并处理编辑表单字段、跳转到上一个/下一个字段、处理自定义键盘、验证数据...

第一个版本基于用于存储表单值的 plist,表单控制器本身保存所有数据。现在我想将存储(模型)与表单控制器分离,因此我决定使用 KVO。

为简单起见,我们假设我有一个设计用于编辑缺勤时间跨度的表单。所以它有两个字段:leaveDatereturnDate

我的模型如下:

@interface Absence
    @property (strong, nonatomic) NSDate *leaveDate;
    @property (strong, nonatomic) NSDate *returnDate;
    @property (readonly, nonatomic) BOOL isValid;
@end

我的表单控制器有一个属性model 指向这个对象。

当用户点击我的 XIB 中的“离开日期”文本字段时,表单控制器会根据我模型的 leaveDate 的当前值提交并显示一个日期选择器。当用户选择其他日期时,表单控制器会使用setValue:forKey: 更新其模型。

isValid 属性被声明为受到leaveDatereturnDate 的影响(使用+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

ControllerAModel的注册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


【解决方案1】:

您似乎很关心性能。我不会的。绘图由主运行循环合并,因此设置textField.text = @"foo"; 不应导致绘图、图像处理等在线发生。通常,像这样的 setter 会设置它的值,然后调用 [self setNeedsDisplay],它只设置一个标志(非常便宜),然后在运行循环结束时,绘图系统将触发一次重绘。你可以设置textField.text一千次,仍然应该只有一次绘制操作。

正如评论者所建议的那样,您应该这样做,以便您的控制器能够容忍多次更新。如果您正在与 setter 一起进行大量工作,请不要这样做。二传手应该是“愚蠢的”。他们应该设置值,并在必要时设置一个标志(如setNeedsDisplay)。在这种情况下,你应该避免在 setter 中做“真正的工作”。

正如另一位评论者所建议的,您也可以不费心在线更新 UI,让 KVO 将更改传播给所有观察者,包括导致更改的控制器。 p>

确实,这些方法中的任何一种都可以,但我怀疑您对性能的担忧是没有根据的。如果存在性能问题,问题不在于有多个更新,而在于您在每次更新期间都在做真正的工作,此时您应该设置一个标志并稍后再做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    • 2012-07-20
    • 2015-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-30
    相关资源
    最近更新 更多