在我的ReactiveCocoa测试项目中了解ReactiveCocoa和MVVM [英] Understanding ReactiveCocoa and MVVM in my ReactiveCocoa test project

查看:54
本文介绍了在我的ReactiveCocoa测试项目中了解ReactiveCocoa和MVVM的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个非常简单的ReactiveCocoa测试应用程序,以尝试在RAC中进行编码(而不是无休止地阅读它).它在.)

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

我在这里可能会遗漏您的观点,但是我认为您所描述的很好.这可以回到上面有关属性和信号之间关系的观点.

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

FWIW,我将稍作更改:

  RAC(self,tickString)= [[[[__tickeraccumulateSignal]deliveryOn:[RACScheduler mainThreadScheduler]//从0开始.startWith:@(0)]地图:^(NSNumber * tick){//解压值,然后格式化用户界面的字符串.NSUInteger计数= tick.unsignedIntegerValue;返回[NSString stringWithFormat:@%i tick%@启动以来",计数,(count!= 1?@"s":@"))];}]; 

这样,我们可以更明确地定义 tickString ticker 的某种转换之间的关系(而且我们可以避免做强/弱的 self 跳舞).

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

由于疲倦,我可能只是想念它,但是您在这里想到的是什么侮辱?

I've written a very simple ReactiveCocoa test application to try my hand at coding in RAC (rather than just reading about it endlessly). It's on Github, and I wanted to get some specific questions about it answered. I'll link to the code components as I go along.

First, a brief explanation of the application: it's a timer-driven iteration counter that can be paused by the user. (Its purpose is to count how many seconds have elapsed, eliding the ones where the user paused it.) Once a second, a timer increments a variable iff the user hasn't paused the incrementing behaviour.

There are three classes I'm concerned about hearing feedback for:

  • MPSTicker (.m), which performs "accumulate since initialization unless paused" and provides that result on a signal. It has a public BOOL property to control whether or not accumulation is running.
  • MPSViewModel (.m), which provides a ViewModel wrapping from MPSTicker to the view controller. It provides read-only strings which show the number of ticks and show the text for the action which, if taken, "pauses" or "resumes" ticks. It also has a read-write BOOL for pausing/unpausing ticks.
  • MPSViewController (.m), which consumes strings from MPSViewModel by binding a label to the ViewModel's tick string, binding a button's text to the "tick action" string, and mapping a button's press into the ViewModel's paused property.

My questions:

  1. 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?)
  2. 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.
  3. 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

(Notes: I think I shied away from a signal for the BOOL of paused on MPSTicker because I didn't know how to consume it in the ViewModel to derive two strings (one for the current tick count, and one for the action text), nor how to push UI-driven value changes when the user pushes the "pause" or "resume" button. This is my core concern in questions 1 and 3.)

Some screenshots to help you visualize this gorgeous design:

Ticking:

Paused:

解决方案

This is such an awesome write-up!

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?)

Broadly, there's nothing wrong or non-Reactive about using properties. KVO-able properties can be thought of as behaviors in the academic FRP sense: they're signals which have a value at all points in their lifetime. In fact, in Objective-C properties can be even better than signals because they preserve type information that we'd otherwise lose by wrapping it in a RACSignal.

So there's nothing wrong with using KVO-able properties if it's the right tool for the job. Just tilt your head, squint a bit, and they look like signals.

Whether something should be a property or RACSignal is more about the semantics you're trying to capture. Do you need the properties (ha!) of a property, or do you care more about the general idea of a value changing over time?

In the specific case of MPSTicker, I'd argue the transitions of accumulateEnabled are really the thing you care about.

So if MPSTicker had a accumulationEnabledSignal property, we'd do something like:

_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);
    }];

We're combining both the tick and the enabledness, since it's the transitions of both that drive our logic.

(FWIW, RACCommand is similar and uses an enabled signal: 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.

I may be missing your point here, but I think what you've described is fine. This goes back to the above point about the relationship between properties and signals.

With RAC and MVVM, a lot of the code is simply threading data through to other parts of the app, transforming it as needed in its particular context. It's about the flow of data through the app. It's boring—almost mechanical—but that's kinda the point. The less we have to re-invent or handle in an ah hoc way, the better.

FWIW, I'd change the implementation slightly:

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" : @"")];
    }];

That way we're more explicitly defining the relationship of tickString to some transformation of ticker (and we can avoid doing the strong/weak self dance).

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

I'm probably just missing it due to tiredness, but what's the indignity you have in mind here?

这篇关于在我的ReactiveCocoa测试项目中了解ReactiveCocoa和MVVM的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆