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

And*_*ndi 8 objective-c reactive-cocoa

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

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

好的,我想做的是:

要求A:

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

要求B:

  • 即使A已经完成了sendNext,也应尽快执行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;
}
Run Code Online (Sandbox Code Playgroud)

我知道这个解决方案不符合我的要求,所以我想知道是否有人可以帮助我找到更好的解决方案.

我的解决方案(源自@ 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
    ]];
}
Run Code Online (Sandbox Code Playgroud)

Jus*_*ers 12

这是一个很难回答的问题,因为您所需的错误处理从根本上与RACSignalAPI契约不兼容,后者声明错误具有异常语义.

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

RACSubject *remoteErrors = [RACSubject subject];
Run Code Online (Sandbox Code Playgroud)

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

我将继续使用remoteSignalcacheSignal您在上面给出了一些修改.这是我们想要的行为:

  1. remoteSignal 应该发送错误 remoteErrors
  2. cacheSignal应该在remoteSignal发送值后立即取消
  3. 任何一个信号的错误都不应该终止另一个
  4. 我们希望合并来自cacheSignal和的值remoteSignal,以便在读取缓存后仍然获取远程值

考虑到这一点,让我们来看看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];
Run Code Online (Sandbox Code Playgroud)

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

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

RACSignal *cacheSignal = [[[IssueWSRequest
    instance]
    issueWithId:issueId cachePolicy:NSURLRequestReturnCacheDataDontLoad]
    takeUntil:remoteSignal];
Run Code Online (Sandbox Code Playgroud)

最后,我们想要合并它们的值,以便两个信号同时启动,它们的值可以按任何顺序到达:

return [RACSignal merge:@[
    [cacheSignal catchTo:[RACSignal empty]],
    [remoteSignal catchTo:[RACSignal empty]]
];
Run Code Online (Sandbox Code Playgroud)

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

并且,尽管合并,使用-takeUntil:on cacheSignal确保它不可能在之后发送值remoteSignal.

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

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