双向KVO:控制器更新模型,通知控制器

时间:2013-02-14 13:42:52

标签: objective-c model-view-controller key-value-observing

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

第一个版本基于用于存储表单值的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通知。我不想要这个。

1 个答案:

答案 0 :(得分:2)

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

正如评论者建议的那样,您应该这样做,以便您的控制器能够容忍多次更新。如果你正在与一个setter进行一系列的工作,那就不要了。塞特斯应该是“哑巴”。他们应该设置值,并在必要时设置标志(如setNeedsDisplay)。在这种情况下,你应该避免在setter中做“真正的工作”。

正如另一位评论者建议的那样,你也可以不费心地更新内嵌用户界面,让KVO向所有观察者发出改变,包括引起改变的控制器。

真的,任何这些方法都可行,但我怀疑你的表现问题是没有根据的。如果存在性能问题,问题不在于有多个更新,而是您在每次更新期间都在做实际工作,那时您应该设置标志并稍后再进行工作。