2013-02-14 59 views
0

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

第一个版本基于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通知。我不想要这个。

+0

刚刚听到KVO通知,你刚刚做出了什么改变?我认为这种选择比删除并重新添加自己作为KVO观察员更“不适合并且有潜在危害”。 – occulus 2013-02-14 16:02:06

+0

我不想执行'textField.text = @“something”;'当我在更新模型之前做的最后一件事情是'textField.text = @“something”;'。想象一下,这是一个更经济的用户界面更新(重新绘制大型控件,图像处理...),经常发生(离散控制,如滑块)。 – Cyrille 2013-02-14 16:03:29

+0

如果您确定接收通知是有效的幂等操作,则多次接收相同的通知无关紧要。 – occulus 2013-02-14 16:03:59

回答

2

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

正如评论者所建议的,您应该让它成为您的控制器可以容忍多个更新。如果你正在做一系列的工作,那就不要这样做。安装者应该是“愚蠢的”。他们应该设置该值,并在必要时设置标志(如setNeedsDisplay)。在这样的情况下,你应该避免在制定者中做“真正的工作”。

正如一位网民建议,你也可能只是懒得更新在线的用户界面,让志愿波及了改变所有的观察员,包括引起的变化,则控制器。

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