如何将两个异步网络调用与ReactiveCocoa组合在一起

时间:2013-12-04 12:47:31

标签: objective-c reactive-cocoa

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

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

好的,我想做的是:

请求A:

  • 应该尽快发送sendNext。
  • 如果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
    ]];
}

1 个答案:

答案 0 :(得分:12)

这是一个很难回答的问题,因为您所需的错误处理从根本上与RACSignal API合同不符,后者声明errors have exception semantics

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

RACSubject *remoteErrors = [RACSubject subject];

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

我会继续使用上面给出的remoteSignalcacheSignal进行一些修改。这是我们想要的行为:

  1. remoteSignal应将错误发送至remoteErrors
  2. {li> cacheSignal应在remoteSignal发送值后立即取消
  3. 来自任何一个信号的错误都不应该终止另一个
  4. 我们希望合并cacheSignalremoteSignal中的值,以便在读取缓存后仍然获取远程值
  5. 考虑到这一点,让我们来看看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]]
    ];
    

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

    尽管合并,但-takeUntil:上使用cacheSignal可确保remoteSignal之后无法发送值。

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

    1. [-doError:] remoteSignal应将错误发送至remoteErrors
    2. {li> [-takeUntil:] cacheSignal应在remoteSignal发送值后立即取消
    3. [-catchTo:] 来自任何一个信号的错误都不应该终止其他
    4. [+merge:] 我们希望合并cacheSignalremoteSignal中的值,以便在读取缓存后仍然获取远程值