【问题标题】:How to combine two async network calls with ReactiveCocoa如何将两个异步网络调用与 ReactiveCocoa 结合起来
【发布时间】:2013-12-20 23:12:46
【问题描述】:

我想合并两个网络信号,但有一些限制。

让我们调用网络信号 A 和 B。A 确实使用 AFNetworking 在缓存中查找资源并立即返回对该请求的任何响应。 B 也考虑缓存,但可以到远程服务器重新验证响应。

好的,那我想做什么:

请求 A:

  • 应该尽快发送下一个。
  • 如果 B 已经完成了 sendNext,我们将忽略 A。
  • 如果出现问题,而 A 产生错误,我们应该忽略它。

请求 B:

  • 应该尽快执行 sendNext,即使 A 已经执行了 sendNext。
  • 如果出现问题,我对 B 的错误很感兴趣,但它不应该阻止 A。

我目前的解决方案是这样的:

- (RACSignal *)issueById:(NSString *)issueId {

    RACSignal *filterSignal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
        RACSignal *cacheSignal = [[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad];

        return [cacheSignal subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            NSLog(@"Ignore error");
            [subscriber sendCompleted];
        } completed:^{
            [subscriber sendCompleted];
        }];
    }];

    RACSignal *remoteSignal = [[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestUseProtocolCachePolicy];

    RACSignal *combined = [RACSignal merge:@[newSign, remoteSignal]];
    return combined;
}

我知道这个解决方案不能满足我的要求,所以我想知道是否有人可以帮助我提供更好的解决方案。

我的解决方案(来自@JustinSpahr-Summers 的回答):

- (RACSignal *)issueById:(NSString *)issueId {

    RACSubject *localErrors = [RACSubject subject];

    RACSignal *remoteSignal = [[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestUseProtocolCachePolicy];

    RACSignal *cacheSignal = [[[[[[IssueWSRequest instance] issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad] 
            takeUntil:remoteSignal] doError:^(NSError *error) {
                [localErrors sendNext:error];
            }] finally:^{
                // Make sure to complete the subject, since infinite signals are
                // difficult to use.
                [localErrors sendCompleted];
            }]
            replayLazily];

    return [RACSignal merge:@[
            [cacheSignal catchTo:[RACSignal empty]],
            remoteSignal
    ]];
}

【问题讨论】:

  • RAC 3.0(尚未发布)有一个操作符-takeUntilReplacement:,它的作用与此非常相似。您可能想查看它的实现以获取想法。
  • @Andi 我昨晚深夜发布了一个答案,但我实际上并没有测试我的建议以确保它有效。好奇它是否会起作用,我今天早上对其进行了测试,发现我对 RAC 在错误/完成时终止订阅的行为做出了错误的假设。简而言之,您似乎无法完全使用-materialize/-dematerialize 抑制错误/完成事件。所以我已经删除了答案。
  • @JustinSpahr-Summers:感谢您的提示 - 不确定它如何涵盖规范,但我会看看它。

标签: objective-c reactive-cocoa


【解决方案1】:

这是一个很难回答的问题,因为您想要的错误处理与 RACSignal API 合同根本不兼容,后者声明 errors have exception semantics

忽略但仍然关心错误的唯一方法是将它们重定向到别处。在这个例子中,我将使用一个主题:

RACSubject *remoteErrors = [RACSubject subject];

...但您也可以使用属性或其他一些通知机制。

我将继续使用您在上面给出的remoteSignalcacheSignal,并进行一些修改。这是我们希望他们的行为:

  1. remoteSignal 应将其错误发送至 remoteErrors
  2. cacheSignal 应在 remoteSignal 发送值后立即取消
  3. 任一信号的错误不应终止另一个信号
  4. 我们要合并cacheSignalremoteSignal的值,这样缓存读取后我们仍然可以得到远程值

考虑到这一点,我们来看看remoteSignal

RACSignal *remoteSignal = [[[[[IssueWSRequest
    instance]
    issueWithId:issueId cachePolicy:NSURLRequestUseProtocolCachePolicy]
    doError:^(NSError *error) {
        [remoteErrors sendNext:error];
    }]
    finally:^{
        // Make sure to complete the subject, since infinite signals are
        // difficult to use.
        [remoteErrors sendCompleted];
    }]
    replayLazily];

-doError:-finally: 控制 remoteErrors 主题,满足我们上面的第一个要求。因为我们需要在多个地方使用remoteSignal(您可以在上面的列表中看到),所以我们使用-replayLazily 来确保它的副作用只发生一次。

cacheSignal 几乎没有变化。我们只需要使用-takeUntil: 来确保它在remoteSignal 发送值时终止(但如果它发送错误则不会):

RACSignal *cacheSignal = [[[IssueWSRequest
    instance]
    issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad]
    takeUntil:remoteSignal];

最后,我们要合并它们的值,这样两个信号同时开始,它们的值可以任意顺序到达:

return [RACSignal merge:@[
    [cacheSignal catchTo:[RACSignal empty]],
    [remoteSignal catchTo:[RACSignal empty]]
];

我们在这里忽略错误,因为任何一个错误都会终止两者(因为它们现在已经合并)。上面已经处理了我们的错误处理行为。

而且,尽管进行了合并,但在 cacheSignal 上使用 -takeUntil: 确保它不可能在 remoteSignal 之后发送值。

再看一下需求列表,您可以看到用于满足每个需求的运算符:

  1. [-doError:] remoteSignal 应将其错误发送至remoteErrors
  2. [-takeUntil:] cacheSignal 应在remoteSignal 发送值后立即取消
  3. [-catchTo:] 来自任一信号的错误不应终止另一个信号
  4. [+merge:]我们要合并cacheSignalremoteSignal的值,这样缓存读取后我们仍然可以得到远程值

【讨论】:

  • 感谢您的精彩回答!你说的很清楚,我马上测试一下。
  • 应该忽略来自 cacheSignal 的错误,而不是远程(理想情况下,如果存在“有效”缓存,则可以忽略来自远程的错误)。我根据您在原始帖子中的回答编写了我的解决方案。我做了一些单元测试,结果看起来很有希望。
  • 我可能会跳过 localErrors。
猜你喜欢
  • 2017-10-31
  • 2021-09-28
  • 2021-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多