【问题标题】:iOS - Re-Attempt Failed NSURLRequests In NSURLSessioniOS - 在 NSURLSession 中重新尝试失败的 NSURLRequests
【发布时间】:2016-01-16 05:26:25
【问题描述】:

在我的应用程序中,对我的服务器的 2-4 个 API 调用可以在我的 API 类的 NSURLSession 内同时(异步)发生。为了向我的服务器发出 API 请求,我必须在每个 NSURLRequestHTTPHeaderField 中提供身份验证令牌。令牌有效期为一天,如果一天后失效,我需要刷新令牌。

我在我的 API 类中的以下代码中执行此操作:

/*!
 * @brief sends a request as an NSHTTPURLResponse. This method is private.
 * @param request The request to send.
 * @param success A block to be called if the request is successful.
 * @param error A block to be called if the request fails.
 */
-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
    {
        [self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
        {
            //if auth token expired and getting "not authenticated" error (status 401)
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 401) {
                [self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
                    self.authToken = response[@"token"];
                    //attempt to re-try the request that failed due to token expiration
                    [self sendTask:request successCallback:success errorCallback:errorCallback];
                } errorCallback:^(NSString *error) {
                    //two weeks have passed and the token is no longer refreshable
                    NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
                }];
            }
        }];
    }];
    [task resume];
}

sendTask 方法会在我在应用程序中发出的每个 API 请求中执行,所以我才意识到这是一种不好的做法。如果 3 个 API 请求由于令牌无效(一天过去)而失败,则所有这 3 个 API 请求都将尝试进行 API 调用以刷新身份验证令牌。

如果有一个 API 请求失败,我是否可以只刷新一次身份验证令牌,然后重新尝试失败的 API 调用?

编辑

我编辑了问题的标题以表明我正在使用 NSURLSession

进展

到目前为止,为了防止多个失败的 API 请求同时尝试刷新身份验证令牌,我为所有失败的请求设置了一个 NSArray 和一个用作锁定的 NSNumber,以确保身份验证令牌仅尝试刷新一次。我在以下代码中执行此操作:

-(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
{
    NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
    {
        MyAPIInterface *__weak weakSelf = self;
        [self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
        {
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 401) {
                if ([error isEqualToString:@"invalid_credentials"]) {
                    errorCallback(@"Invalid username and/or password");
                }
                else if ([error isEqualToString:@"Unknown error"]) {
                    errorCallback(error);
                }
                else {
                    if (!weakSelf.alreadyRefreshingToken.boolValue) {

                        //lock alreadyRefreshingToken boolean
                        weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:YES];
                        NSLog(@"NOT REFRESHING TOKEN");

                        // add failed request to failedRequests array
                        NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
                        [mutableFailedRequests addObject:request];
                        weakSelf.failedRequests = [mutableFailedRequests copy];

                        // refresh auth token
                        [weakSelf refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {

                            //store authToken
                            weakSelf.authToken = response[@"token"];
                            NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
                            [defaults setObject:weakSelf.authToken forKey:@"authToken"];
                            [defaults synchronize];

                            //attempt to re-try all requests that failed due to token expiration
                            for (NSURLRequest *failedRequest in weakSelf.failedRequests) {
                                [weakSelf sendTask:failedRequest successCallback:success errorCallback:errorCallback];
                            }

                            //clear failedRequests array and unlock alreadyRefreshingToken boolean
                            [weakSelf clearFailedRequests];
                            weakSelf.alreadyRefreshingToken = [NSNumber numberWithBool:NO];

                            NSLog(@"TOKEN REFRESHING SUCCESSFUL THO");

                        } errorCallback:^(NSString *error) {

                            NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");

                            //clear failedRequests array
                            [weakSelf clearFailedRequests];

                            errorCallback(@"Your login session has expired");

                        }];
                    }
                    else  {
                        NSLog(@"ALREADY REFRESHING TOKEN. JUST ADD TO FAILED LIST");
                        NSMutableArray *mutableFailedRequests = [weakSelf.failedRequests mutableCopy];
                        [mutableFailedRequests addObject:request];
                        weakSelf.failedRequests = [mutableFailedRequests copy];
                    }
                }
            }
            else {
                NSLog(@"ERROR STRING THO: %@", error);
                errorCallback(error);
            }
        }];
    }];
    [task resume];
}

#pragma mark Custom Methods

-(void)clearFailedRequests {
    NSMutableArray *mutableFailedRequests = [self.failedRequests mutableCopy];
    [mutableFailedRequests removeAllObjects];
    self.failedRequests = [mutableFailedRequests copy];
}

我这样做对吗?我偏执的一部分是我在某些时候并没有真正调用successerror 回调。这会导致问题吗?

【问题讨论】:

    标签: ios api authentication nsurlsession nsurlrequest


    【解决方案1】:

    不要使用 [self sendTask:],而是尝试使用 [weakSelf sendTask]。检查以下代码:

    -(void)sendTask:(NSURLRequest*)request successCallback:(void (^)(NSDictionary*))success errorCallback:(void (^)(NSString*))errorCallback
    {
    
       __weak __typeof(self)weakSelf = self;
        NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            [self parseResponse:response data:data fromRequest:request successCallback:success errorCallback:^(NSString *error)
            {
                //if auth token expired and getting "not authenticated" error (status 401)
                NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
                if (httpResp.statusCode == 401) {
                    [self refreshAuthenticationTokenWithSuccessCallback:^(NSDictionary *response) {
                        self.authToken = response[@"token"];
                        //attempt to re-try the request that failed due to token expiration
                        [weakSelf sendTask:request successCallback:success errorCallback:errorCallback];
                    } errorCallback:^(NSString *error) {
                        //two weeks have passed and the token is no longer refreshable
                        NSLog(@"TOKEN NOT REFRESHABLE! HAVE TO LOG IN MANUALLY");
                    }];
                }
            }];
        }];
        [task resume];
    }
    

    【讨论】:

    • 糟糕,感谢您的关注。我忘了把虚弱的自己放进去。但这将如何防止多个失败的请求同时尝试刷新令牌?
    • 不会,但只能解决重试问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 2015-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-08
    相关资源
    最近更新 更多