在我的应用程序中,对我的服务器的 2-4 个 API 调用可以在我的 API 类的
NSURLSession
中同时(异步)发生。为了向我的服务器发出 API 请求,我必须在每个 HTTPHeaderField
的 NSURLRequest
中提供身份验证令牌。 token有效期为一天,如果一天后失效,我需要刷新token。
我在 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];
}
我的做法正确吗?我偏执的一点是,我并没有真正在某些时刻调用
success
或 error
回调。这会导致问题吗?
不要使用 [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];
}