了解ReactiveCocoa测试项目中的ReactiveCocoa和MVVM

我写了一个非常简单的ReactiveCocoa测试应用程序,试着在RAC中编写代码(而不是只是无休止地阅读它)。 这是在Github上 ,我想得到一些有关它的具体问题。 我将继续链接到代码组件。

首先,简要说明应用程序:它是一个计时器驱动的迭代计数器,可以由用户暂停。 (它的目的是计算已经过了多少秒,忽略了用户暂停它的那些秒。)每秒一次,如果用户没有暂停递增行为,则计时器会增加变量。

我关注的三个课程是听取反馈意见:

  • MPSTicker ( .m ),执行“自初始化后累积,除非暂停”并在信号上提供该结果。 它有一个公共BOOL属性来控制累积是否正在运行。
  • MPSViewModel ( .m ),它提供从MPSTicker到视图控制器的ViewModel包装。 它提供只读字符串,显示刻度数并显示操作的文本,如果采用,则“暂停”或“恢复”刻度。 它还具有用于暂停/取消暂停刻度的读写BOOL
  • MPSViewController ( .m ),它通过将标签绑定到ViewModel的tick字符串,将按钮的文本绑定到“tick action”字符串,并将按钮的按下映射到ViewModel的暂停属性来消耗MPSViewModel的字符串。

我的问题:

  1. 我不喜欢MPSTicker上的BOOL属性启用/禁用它的累积,但我不知道怎么做更多Reactive-ly。 (这也在ViewModel和ViewController的下游运行:如何通过所有这三个字符串运行字符串来控制自动收报机是否正在运行?)
  2. ViewModel将tickStringtickStateString公开为非常传统的属性,但使用它们的ViewController会立即将它们映射回带有RACObserve 的标签和按钮文本 上的 文本 。 这感觉不对,但我不知道如何从ViewModel公开一个信号,ViewController很容易为这两个属性使用。
  3. 在ViewModel上翻转paused BOOL时,ViewController会受到侮辱 。 我认为这是#1的另一个下游效应,“这不应该是BOOL属性”,但我不确定

(注意:我想我避开了MPSTicker paused BOOL的信号,因为我不知道如何在ViewModel中使用它来派生两个字符串(一个用于当前的滴答计数,一个用于操作文本)当用户按下“暂停”或“恢复”按钮时,也不会如何推动UI驱动的值更改。这是我在问题1和3中的核心问题。)

一些截图可以帮助您想象这个华丽的设计:

滴答:

滴答

已暂停:

已暂停

这是一个非常棒的写作!

我不喜欢MPSTicker上的BOOL属性启用/禁用它的累积,但我不知道怎么做更多Reactive-ly。 (这也在ViewModel和ViewController的下游运行:如何通过所有这三个字符串运行字符串来控制自动收报机是否正在运行?)

从广义上讲,使用属性没有任何错误或非反应。 KVO能力属性可以被认为是学术FRP意义上的行为:它们是在其生命周期的所有点都具有价值的信号。 事实上,在Objective-C中,属性甚至可以比信号更好,因为它们保留了我们通过将其包装在RACSignal丢失的类型信息。

因此,如果它是适合工作的正确工具,那么使用KVO-able属性没有任何问题。 只是倾斜你的头,斜视一下,它们看起来像信号。

某些东西应该是属性还是RACSignal更多地是关于你想要捕获的语义。 您是否需要财产的属性(ha!),或者您是否更关心价值随时间变化的一般概念?

MPSTicker的具体案例中,我认为accumulateEnabled的转换确实是你关心的事情。

因此,如果MPSTicker有一个accumulationEnabledSignal属性,我们会做类似的事情:

 _accumulateSignal = [[[[RACSignal combineLatest:@[ _tickSignal, self.accumulationEnabledSignal ]] filter:^(RACTuple *t) { NSNumber *enabled = t[1]; return enabled.boolValue; }] reduceEach:^(NSNumber *tick, NSNumber *enabled) { return tick; }] scanWithStart:@(0) reduce:^id(NSNumber *previous, id next) { // On each tick, we add one to the previous value of the accumulate signal. return @(previous.unsignedIntegerValue + 1); }]; 

我们将tick和enableness结合起来,因为它是两者的转换驱动我们的逻辑。

(FWIW, RACCommand类似并使用启用的信号: https : //github.com/ReactiveCocoa/ReactiveCocoa/blob/9503c6ef7f2f327f4db6440ddfbc4ee09b86857f/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h#L95 。)

ViewModel将tickString和tickStateString公开为非常传统的属性,但使用它们的ViewController会立即将它们映射回带有RACObserve的标签和按钮文本上的文本。 这感觉不对,但我不知道如何从ViewModel公开一个信号,ViewController很容易为这两个属性使用。

我可能在这里错过了你的观点,但我认为你所描述的很好。 这可以追溯到关于属性和信号之间关系的上述观点。

使用RAC和MVVM,许多代码只是将数据线程化到应用程序的其他部分,并根据需要在其特定上下文中进行转换。 这是关于通过应用程序的数据流。 它很无聊 – 几乎是机械的 – 但这有点重要。 我们越需要以一种特殊的方式重新发明或处理,就越好。

FWIW,我稍微改变了实现:

 RAC(self, tickString) = [[[[_ticker accumulateSignal] deliverOn:[RACScheduler mainThreadScheduler]] // Start with 0. startWith:@(0)] map:^(NSNumber *tick) { // Unpack the value and format our string for the UI. NSUInteger count = tick.unsignedIntegerValue; return [NSString stringWithFormat:@"%i tick%@ since launch", count, (count != 1 ? @"s" : @"")]; }]; 

这样我们就可以更明确地定义tickStringticker转换的关系(我们可以避免做强/弱的self舞蹈)。

在ViewModel上翻转暂停的BOOL时,ViewController会受到侮辱。 我认为这是#1的另一个下游效应,“这不应该是BOOL属性”,但我不确定

我可能只是因为疲倦而错过了它,但你在这里想到的是什么?