首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >拆分RACSignal以消除状态

拆分RACSignal以消除状态
EN

Stack Overflow用户
提问于 2013-09-17 14:37:59
回答 1查看 3.4K关注 0票数 14

我使用ReactiveCocoa更新UILabel,而UIProgressView则向下计数:

代码语言:javascript
复制
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...
    }];

这很好,但是,我对count变量或读取self.progressView.progress值以减少它并不特别满意。

我觉得我应该能够使用RAC宏直接发送信号并绑定属性。类似于:

代码语言:javascript
复制
RACSignal *baseSignal = [[RACSignal interval:0.05 onScheduler:[RACScheduler mainThreadScheduler]]
                            take: percentRemaining]

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

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

...揭示了我被困在哪里。我无法完全理解如何编写RACSignal,因此不需要依赖状态变量。

此外,当流完成时,我不确定在哪里/如何注入我需要的// Move along...副作用。

我相信,只要你有正确的想法,两者都是足够简单的,但是,任何帮助都是非常感谢的。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-09-17 22:00:23

当有疑问时,请查看RACSignal+Operations.hRACStream.h,因为您想要做的事情肯定会有一个操作员。在本例中,缺少的基本部分是-scanWithStart:reduce:

首先,让我们看一下baseSignal。逻辑将基本保持不变,只是我们应该为其发布连接

代码语言:javascript
复制
RACMulticastConnection *timer = [[[RACSignal
    interval:0.05 onScheduler:[RACScheduler mainThreadScheduler]]
    take:percentRemaining]
    publish];

这样我们就可以在所有相关信号之间共享一个定时器。尽管您提供的baseSignal也能工作,但这将为每个用户重新创建一个定时器(包括依赖信号),这可能会导致它们的触发发生微小的变化。

现在,我们可以使用-scanWithStart:reduce:来增加countLabel和减少progressView。该操作符接受先前的结果和当前值,并允许我们任意转换或组合它们。

但是,在我们的示例中,我们只想忽略当前值(由NSDate发送的+interval:),因此我们只需操作前一个值:

代码语言:javascript
复制
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:操作符看起来可能是多余的,但这对于确保在timer.signal发送任何内容之前设置了textprogress是必要的。

然后,我们将使用一个正常的订阅完成。这些副作用也完全有可能转化为信号,但如果不看到代码,很难知道:

代码语言:javascript
复制
[timer.signal subscribeCompleted:^{
    // Move along...
}];

最后,因为我们使用了上面的RACMulticastConnection,所以还没有任何东西真正启动。必须手动启动连接:

代码语言:javascript
复制
[timer connect];

这将连接所有上述订阅,并启动计时器,因此这些值开始流向属性。

现在,这显然是更多的代码,而不是命令式的等价物,所以人们可能会问为什么它是值得的。有几个好处:

  1. 值计算现在是thread-safe,,因为它们不依赖于副作用。如果您需要实现一些更昂贵的东西,那么将重要的工作转移到后台线程是非常容易的。
  2. 类似地,值计算是相互独立的。如果它们变得有价值,它们可以很容易地并行化。
  3. 所有的逻辑现在都是绑定本地的。您不必怀疑更改来自何处或担心排序(例如,初始化和更新之间的顺序),因为它们都在一个地方,可以自上而下地读取。
  4. 这些值可以在不引用视图的情况下计算。例如,在模型-视图-视图模型中,计数和进度实际上将在一个视图模型中确定,然后视图层只是一组哑绑定。
  5. 更改的值来自,只有一个输入。如果您突然需要合并另一个输入源(例如,真正的进度而不是计时器),那么您只需要更改一个地方。

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

尽管命令式代码可以从一开始就变得不那么复杂,但它的复杂性却呈指数级增长。功能代码(尤其是功能反应代码)可能一开始就更复杂,但随后其复杂性线性增长--随着应用程序的增长,管理起来要容易得多。

票数 38
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18852844

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档