ios - 在我的 ReactiveCocoa 测试项目中了解 ReactiveCocoa 和 MVVM

标签 ios mvvm reactive-cocoa

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

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

我关心三个类的听觉反馈:

  • MPSTicker ( .m ),它执行“自初始化以来累积,除非暂停”并在信号上提供该结果。它有一个公共(public)的BOOL属性来控制是否运行累加。
  • MPSViewModel ( .m ),它提供了一个从 MPSTicker 到 View Controller 的 ViewModel 包装。它提供只读字符串,显示滴答数并显示操作文本,如果采取该操作,“暂停”或“恢复”滴答。它还有一个可读写的 BOOL 用于暂停/取消暂停报价。
  • MPSViewController ( .m ),它通过将标签绑定(bind)到 ViewModel 的 tick 字符串,将按钮的文本绑定(bind)到“tick action”字符串,并将按钮的按下映射到 ViewModel 的 paused 属性来使用 MPSViewModel 中的字符串.

我的问题:

  1. 我不喜欢 BOOL property on MPSTicker用于启用/禁用它的积累,但我不知道如何更被动地做到这一点。 (这也在 ViewModel 和 ViewController 的下游运行:我如何通过所有这三个运行一个字符串来控制代码是否正在运行?)
  2. ViewModel 公开了 tickStringtickStateString作为非常传统的属性,但是使用这些属性的 ViewController 会立即将它们映射回 text on a labelbutton text使用 RACObserve。这感觉不对,但我不知道如何公开来自 ViewModel 的信号,以便 ViewController 轻松使用这两个属性。
  3. ViewController 遇到了 indignity在 ViewModel 上翻转 paused BOOL 时。我认为这是 #1 的另一个下游效应,“这不应该是 BOOL 属性”,但我不确定

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

一些屏幕截图可以帮助您形象化这个华丽的设计:

滴答作响:

Ticking

暂停:

Paused

最佳答案

这是一篇很棒的文章!

I don't like the BOOL property on MPSTicker for enabling/disabling its accumulation, but I didn't know how to do it more Reactive-ly. (This also runs downstream to the ViewModel and ViewController: how can I run a string through all three of these to control whether or not the ticker is running?)

从广义上讲,使用属性并没有错或非响应式。 KVO-able 属性可以被认为是学术 FRP 意义上的行为:它们是在其生命周期的所有时间点都具有值(value)的信号。事实上,在 Objective-C 中,属性甚至比信号更好,因为它们保留了类型信息,否则我们会通过将其包装在 RACSignal 中而丢失这些信息。

因此,如果它是完成这项工作的正确工具,那么使用支持 KVO 的属性并没有错。只要倾斜你的头,稍微眯一下,它们看起来就像信号。

某物应该是属性还是 RACSignal 更多是关于您试图捕获的语义。您需要一个属性的特性(哈哈!),还是您更关心值随时间变化的一般概念?

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 和 enabledness 结合起来,因为正是这两者的转换驱动了我们的逻辑。

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

The ViewModel exposes tickString and tickStateString as very traditional properties, but the ViewController which consumes these immediately maps them back into text on a label and button text with RACObserve. This feels wrong, but I don't know how to expose a signal from the ViewModel that's easy for the ViewController to consume for these two attributes.

我可能没有理解您的意思,但我认为您的描述很好。这又回到了上面关于属性和信号之间关系的观点。

使用 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跳舞)。

The ViewController suffers an indignity when flipping the paused BOOL on the ViewModel. I think this is another downstream effect of #1, "This shouldn't be a BOOL property", but I'm not sure

我可能只是因为疲倦而想念它,但你在这里想到的侮辱是什么?

关于ios - 在我的 ReactiveCocoa 测试项目中了解 ReactiveCocoa 和 MVVM,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21419864/

相关文章:

ios - UIPickerView 将数据传递给容器 View

iOS 应用程序 - 精简版或付费版

c# - 我是否需要持有至少 1 个 View 模型来处理我的所有 View ?

c# - 订阅 ViewModel 中当前项目的 PropertyChanged

ios - 观察数组并将其折叠成字符串

ios - ReactiveCocoa::我可以在 createsignal 中订阅一个信号吗?

swift - 对 MVVM 中的信号使用react?

ios - 快速从 healthkit 中获取周、月和年格式的用户数据

ios - 按 dateTimeString 属性对 NSMutableArray 对象进行排序

c# - 禁用与当前 View 对应的当前按钮wpf c#