允许异步函数只调用一次(让其他人等待)

时间:2017-11-28 12:34:46

标签: ios objective-c authentication asynchronous

我有一个授权功能,如果它发出未经授权的请求,它将在我的应用程序中运行。我最初在POC中构建了auth功能,现在我已将其转移到我正在处理的应用程序中。我注意到一个有趣的问题,如果我在启动时将令牌设置为无效(这只在启动时发生),那么从应用程序发出大约4个身份验证请求。据我所知,auth的响应在请求发出后返回,因此在应用程序意识到它具有有效令牌之前会有一些堆叠。如果使用应用程序时身份验证令牌过期,该功能将完美运行,此问题纯粹是在加载时。

我已经决定解决这个问题的方法是使auth函数独占。我做了一些研究,允许一次调用函数并遇到@synchronized。这很有道理,但我不太清楚如何将它实现到我的功能中。我的auth功能如下所示:

+ (void)requestAuthTokenWithBlock:(void (^)(void))completion {

    NSLog(@"requestNewToken - Called: Requesting a new authorization bearer token.");

    [[NSURLCache sharedURLCache] removeAllCachedResponses];

    //Build request URL String
    NSString *requestString = [NSString stringWithFormat:@"%@%@",baseURL,authRequestURL];

    //Encode password so that it can be safely sent in request
    NSString *encodedPassword = [kU1Password stringByAddingPercentEncodingForRFC3986];

    //Populate post request with user credentials
    NSString *post = [NSString stringWithFormat:@"client_id=%@&password=%@&grant_type=%@", kU1ClientId, encodedPassword, kU1GrantType];

    //Encode post string & convert to type NSData
    NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];

    //Calculate the length of the post string
    NSString *postLength = [NSString stringWithFormat:@"%lu",(unsigned long)[postData length]];

    //Initialize url request
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];

    //Set the url for which you will pass your request data
    [request setURL:[NSURL URLWithString:requestString]];

    //Set HTTP method for request
    [request setHTTPMethod:@"POST"];

    //Set HTTP header field with length of post data
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];

    //Set the encoded value for HTTP Header field
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

    //Set the HTTP body of the urlrequest with our post data
    [request setHTTPBody:postData];

    //Create full request
    NSURLSession *session = [NSURLSession sharedSession];

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){

                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"Status Code: %ld\n - requestAuthTokenWithBlock - ",(long)httpResponse.statusCode);

                                                    NSString *message = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
                                                    NSLog(@"Message: %@", message);

                                                    //Check for an error, if there is no error we proceed.
                                                    if (!error) {

                                                        NSLog(@"requestAuthToken - Successful responce from server");

                                                        //Populate the auth object with the parse json data (handled entirely in the builder)
                                                        AuthToken *auth = [TokenBuilder authFromJSON:data error:&error];

                                                        //Save the auth & refresh tokens in the keychain
                                                        [SAMKeychain setPassword:auth.AuthToken forService:kServer account:kKeyAccessToken];
                                                        [SAMKeychain setPassword:auth.RefreshToken forService:kServer account:kKeyRefreshToken];

                                                        //Trigger completion block
                                                        if (completion)
                                                        {
                                                            completion();
                                                        }
                                                    }
                                                    else {
                                                        //Failed request

                                                        //Trigger completion block
                                                        if (completion)
                                                        {
                                                            completion();
                                                        }
                                                    }
                                                }];
    [dataTask resume];

}

如果应用程序有一个令牌,它只能从这个switch语句调用:

switch (httpResponse.statusCode) {
case 200 ... 299:{
   NSLog(@"SUCCESS");
   NSLog(@"Performing any completion related functions!");                                                       
   retryAttempts = 0;
   completion(true, message, data, nil);
   return;
   }
case 401:{
   if (retryAttempts == 0){
      NSLog(@"401 Challenge - Retrying Authentication, First Attempt:");
   }
   else {
      NSLog(@"401 Challenge - Retrying Authentication, Attempt %ld", (long)retryAttempts);
   }

   [self requestAuthTokenWithBlock:^(void){
      NSLog(@"requestAuthTokenWithBlock - Completion block called");
      [self dataTaskwithRequest:request method:method completion:completion];
   }];

   retryAttempts += 1;
   break;
   }
}

我复制了@Synchronized代码段,但实际上并不知道该把它放在哪里:

@synchronized (<#token#>) {
        <#statements#>
    }

我尝试将它放入我调用数据任务的令牌方法中:

@synchronized (dataTask) {
        [dataTask resume];
    }

并且该函数仍被多次调用。我不知道如何解决这个问题,我可以在应用程序的任何地方添加对此函数的调用,并且不认为我可以自己控制请求。我认为保护函数免受多次调用的唯一地方就是函数本身。作为旁注,我添加了一个布尔值,该函数在函数启动时设置为false,在函数结束时设置为true。

if (canAttempt == NO) {
        return;
    }

    canAttempt = NO;

现在,曾经被调用4次的函数现在返回失败,因为它们未被授权。关于如何在继续之前让应用程序等待此功能的任何想法?

0 个答案:

没有答案