拆分RACSignal以消除状态 [英] Splitting an RACSignal to eliminate state

查看:168
本文介绍了拆分RACSignal以消除状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用ReactiveCocoa更新 UILabel ,而 UIProgressView 倒计时:

I'm using ReactiveCocoa to update a UILabel whilst a UIProgressView counts down:

NSInteger percentRemaining = ...;
self.progressView.progress = percentRemaining / 100.0;

__block NSInteger count = [self.count];

[[[RACSignal interval:0.05 onScheduler:[RACScheduler mainThreadScheduler]]
    take: percentRemaining]
    subscribeNext:^(id x) {
        count++;
        self.countLabel.text = [NSString stringWithFormat:@"%d", count];
        self.progressView.progress = self.progressView.progress - 0.01;
    } completed:^{
        // Move along...
    }];

这个工作得很好,但是我并不特别满意计数变量或读取 self.progressView.progress 的值以递减它。

This works well enough but, I'm not particularly happy with either the count variable or with reading the value of self.progressView.progress in order to decrement it.

我觉得我应该能够使用 RAC 宏直接调用信号并绑定属性。例如:

I feel like I should be able to spit the signal and bind the properties directly using the RAC macro. Something like:

RACSignal *baseSignal = [[RACSignal interval:0.05 onScheduler:[RACScheduler mainThreadScheduler]]
                            take: percentRemaining]

RAC(self, countLabel.text) = [baseSignal
                                  map: ...
                                  ...

RAC(self, progressView.progress) = [baseSignal
                                        map: ...
                                        ...

... 显示我被卡住的地方。我不能完全理解如何编写 RACSignal ,使我不需要依赖状态变量。

The ...s reveal where I'm stuck. I can't quite get my head around how to compose the RACSignal such that I don't need to rely on a state variable.

此外,我不知道在哪里/如何注入 //移动... 副作用需要当流完成。

Additionally I'm not sure where/how to inject the // Move along... side effect I need when the stream completes.

我确定两者都很简单,一旦你认为正确的方法,但任何帮助将非常感激。

I'm sure both are simple enough once you're thinking the right way but, any help would be really appreciated.

推荐答案

如有疑问,请查看
RACSignal + Operations.h

RACStream.h
,因为有绑定是一个操作符,你想做什么。在这种情况下,
基本缺失的部分是
-scanWithStart:reduce:

When in doubt, check out RACSignal+Operations.h and RACStream.h, because there's bound to be an operator for what you want to do. In this case, the basic missing piece is -scanWithStart:reduce:.

首先,让我们来看看 baseSignal 。逻辑将保持
基本相同,除了我们应该发布 a
连接

为它:

First of all, though, let's look at the baseSignal. The logic will stay basically the same, except that we should publish a connection for it:

RACMulticastConnection *timer = [[[RACSignal
    interval:0.05 onScheduler:[RACScheduler mainThreadScheduler]]
    take:percentRemaining]
    publish];

这样我们可以在所有的依赖
信号之间共享一个定时器。虽然你提供的 baseSignal 也可以工作,但是
会为每个订阅者重新创建一个计时器(包括相关信号),这可能会导致

This is so that we can share a single timer between all of the dependent signals. Although the baseSignal you provided would also work, that'll recreate a timer for each subscriber (including dependent signals), which might lead to tiny variances in their firing.

现在,我们可以使用 -scanWithStart:reduce:增加 countLabel
减少 progressView 。这个操作符取前面的结果和
当前值,让我们转换或组合它们,但我们想要的。

Now, we can use -scanWithStart:reduce: to increment the countLabel and decrement the progressView. This operator takes previous results and the current value, and lets us transform or combine them however we want.

在我们的例子中,忽略当前值( NSDate 通过 + interval:发送
),因此我们可以上一个:

In our case, though, we just want to ignore the current value (the NSDate sent by +interval:), so we can just manipulate the previous one:

RAC(self.countLabel, text) = [[[timer.signal
    scanWithStart:@0 reduce:^(NSNumber *previous, id _) {
        return @(previous.unsignedIntegerValue + 1);
    }]
    startWith:@0]
    map:^(NSNumber *count) {
        return count.stringValue;
    }];

RAC(self.progressView, progress) = [[[timer.signal
    scanWithStart:@(percentRemaining) reduce:^(NSNumber *previous, id _) {
        return @(previous.unsignedIntegerValue - 1);
    }]
    startWith:@(percentRemaining)]
    map:^(NSNumber *percent) {
        return @(percent.unsignedIntegerValue / 100.0);
    }];

上面的 -startWith:可能看起来多余,但这是必须的
,以确保之前设置 text progress $ c> timer.signal
发送了任何东西。

The -startWith: operators in the above may seem redundant, but this is necessary to ensure that text and progress are set before timer.signal has sent anything.

然后,我们将使用正常订阅完成。这完全是
可能的,这些副作用也可以变成信号,但它是
很难知道,而没有看到代码:

Then, we'll just use a normal subscription for completion. It's entirely possible that these side effects could be turned into signals as well, but it's hard to know without seeing the code:

[timer.signal subscribeCompleted:^{
    // Move along...
}];

最后,因为我们使用了 RACMulticastConnection ,没有什么会真的
火。必须手动启动连接:

Finally, because we used a RACMulticastConnection above, nothing will actually fire yet. Connections have to be manually started:

[timer connect];

这会连接上述所有订阅的 ,并启动计时器,所以
值开始流向属性。

This connects all of the above subscriptions, and kicks off the timer, so the values begin flowing to the properties.

现在,这显然比命令式等价,所以一个人可能会
问为什么这是值得的。有几个好处:

Now, this is obviously more code than the imperative equivalent, so one might ask why it's worthwhile. There are several benefits:


  1. 现在,值计算线程安全,因为它们不依赖side
    效果。

  2. 类似地,价值计算是独立的移动到后台线程是非常简单的) >。

  3. 所有的逻辑现在绑定的本地。你不必想知道
    哪里的变化是来自或担心排序(例如,在
    初始化和更新之间),因为它都在一个地方,可以读取
    top-

  4. 可以计算这些值,而无需引用视图。对于
    示例,在 Model-View-ViewModel 中,
    的计数和进度实际上会在查看
    模型

    和那么视图层只是一组哑配对。

  5. 更改的值从只有一个输入。如果你突然需要
    包含另一个输入源(例如,实际进度而不是计时器),
    只有一个地方你需要更改。

  1. The value calculations are now thread-safe, because they don't depend on side effects. If you need to implement something more expensive, it's extremely easy to move the important work to a background thread.
  2. Similarly, the value calculations are independent of each other. They can be easily parallelized if this ever becomes valuable.
  3. All of the logic is now local to the bindings. You don't have to wonder where changes are coming from or worry about the ordering (e.g., between initialization and updating), because it's all in one place and can be read top-down.
  4. The values can be calculated without any reference to a view. For example, in Model-View-ViewModel, the count and progress would actually be determined in a view model, and then the view layer is just a set of dumb bindings.
  5. The changing values flow from only one input. If you suddenly need to incorporate another input source (e.g., real progress instead of a timer), there's only one place you need to change.

基本上,这是命令式和函数式编程的一个典型例子。

Basically, this is a classic example of imperative vs. functional programming.

虽然命令式代码可以开始不那么复杂,在复杂度
按指数方式。功能代码(尤其是功能反应代码)可能会使
开始更复杂,但是它的复杂性会线性增长

Although imperative code can start off less complex, it grows in complexity exponentially. Functional code (and especially functional reactive code) may start off more complex, but then its complexity grows linearly — it's much easier to manage as the application grows.

这篇关于拆分RACSignal以消除状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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