使用ReactiveCocoa重试异步操作 [英] Retrying an asynchronous operation using ReactiveCocoa

查看:106
本文介绍了使用ReactiveCocoa重试异步操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用ReactiveCocoa信号来表示对系统中RESTful后端的调用.每个RESTful调用都应接收一个令牌作为参数之一.令牌本身是通过身份验证API调用接收的.

I'm using ReactiveCocoa signals to represent calls to RESTful backend in our system. Each RESTful invocation should receive a token as one of the parameters. The token itself is received from authentication API call.

一切正常,我们现在介绍了令牌过期,因此如果API调用失败并使用HTTP代码403,则后端访问类可能需要重新授权自己.我想使此操作对调用方完全透明,这是最好的是我想到的:

All works fine and we're now introduced token expiration, so the backend access class may need to reauthorize itself if the API call fails with HTTP code 403. I want to make this operation completely transparent for the callers, this is the best I came up with:

- (RACSignal *)apiCallWithSession:(Session *)session base:(NSString *)base params:(NSDictionary *)params get:(BOOL)get {
    NSMutableDictionary* p = [params mutableCopy];
    p[@"token"] = session.token;

    RACSubject *subject = [RACReplaySubject subject];

    RACSignal *first = [self apiCall:base params:p get:get];  // this returns the signal representing the asynchronous HTTP operation

    @weakify(self);
    [first subscribeNext:^(id x) {
        [subject sendNext:x];   // if it works, all is fine
    } error:^(NSError *error) {
        @strongify(self);

        // if it doesn't work, try re-requesting a token
        RACSignal *f = [[self action:@"logon" email:session.user.email password:session.user.password]
                         flattenMap:^RACStream *(NSDictionary *json) {  // and map it to the other instance of the original signal to proceed with new token
            NSString *token = json[@"token"];

            p[@"token"] = token;
            session.token = token;

            return [self apiCall:base params:p get:get];
        }];

        // all signal updates are forwarded, we're only re-requesting token once            
        [f subscribeNext:^(id x) {
            [subject sendNext:x];
        } error:^(NSError *error) {
            [subject sendError:error];
        } completed:^{
            [subject sendCompleted];
        }];
    } completed:^{
        [subject sendCompleted];
    }];

    return subject;
}

这是正确的方法吗?

推荐答案

首先,.嵌套订阅尤其是一种反模式,通常有信号运算符可以代替它们.

First of all, subscriptions and subjects should generally be avoided as much as possible. Nested subscriptions, in particular, are quite an anti-pattern—usually there are signal operators that can replace them.

在这种情况下,我们需要利用信号可以代表延期工作的事实,并仅创建一个信号来执行实际请求:

In this case, we need to take advantage of the fact that signals can represent deferred work, and create only one signal to perform the actual request:

// This was originally the `first` signal.
RACSignal *apiCall = [RACSignal defer:^{
    return [self apiCall:base params:p get:get];
}];

此处使用+defer:可确保

The use of +defer: here ensures that no work will begin until subscription. An important corollary is that the work can be repeated by subscribing multiple times.

例如,如果遇到错误,我们可以尝试获取令牌,然后返回相同的延迟信号表示应该再次尝试:

For example, if we catch an error, we can try fetching a token, then return the same deferred signal to indicate that it should be attempted again:

return [[apiCall
    catch:^(NSError *error) {
        // If an error occurs, try requesting a token.
        return [[self
            action:@"logon" email:session.user.email password:session.user.password]
            flattenMap:^(NSDictionary *json) {
                NSString *token = json[@"token"];

                p[@"token"] = token;
                session.token = token;

                // Now that we have a token, try the original API call again.
                return apiCall;
            }];
    }]
    replay];

使用-replay替换了以前的RACReplaySubject,并使请求立即开始;但是,它也可以-replayLazily甚至完全消除(每个订阅重做一次呼叫).

The use of -replay replaces the RACReplaySubject that was there before, and makes the request start immediately; however, it could also be -replayLazily or even eliminated completely (to redo the call once per subscription).

就是这样!重要的是要指出,仅需设置要执行的工作就不需要显式订阅.订阅通常只应在程序的叶子"处进行,即调用者实际上要求执行的工作.

That's it! It's important to point out that no explicit subscription was needed just to set up the work that will be performed. Subscription should generally only occur at the "leaves" of the program—where the caller actually requests that work be performed.

这篇关于使用ReactiveCocoa重试异步操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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