Understanding ReactiveCocoa and MVVM in my ReactiveCocoa test project

久未见 提交于 2019-12-04 06:54:58

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?

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!