在iOS中同步调用同步的最佳做法?

时间:2013-03-08 01:36:01

标签: iphone ios objective-c concurrency afnetworking

我有一个单例类,APIClient,它需要先设置userId和authToken才能调用我的后端。

我们目前正在NSUserDefaults中存储userId和authToken。对于全新安装,这些值不存在,我们在服务器中查询它们。

目前,我们在ViewControllers的viewDidLoad方法中有代码,如果这些值不存在,则手动查询服务器。

我有兴趣让这堂课“正常工作”。通过这个,我的意思是让客户端检查它是否已经初始化,如果没有激活对服务器的调用并设置适当的userId和authToken - 所有这些都没有人工干扰。

由于以下原因,这被证明是相当棘手的:

  1. 我无法使asyncObtainCredentials同步,因为#iphonedev的人告诉我,如果我必须冻结网络操作的主线程,操作系统将终止我的应用程序
  2. 对于我们现在所拥有的,由于asyncObtainCredential的异步性质,第一次调用将始终失败。 Nil将被退回,并且第一次通话将始终失败。
  3. 有没有人知道这个问题的好方法?

    `

    @interface APIClient ()
    @property (atomic) BOOL initialized;
    @property (atomic) NSLock *lock;
    @end
    
    @implementation APIClient
    
    #pragma mark - Methods
    
    - (void)setUserId:(NSNumber *)userId andAuthToken:(NSString *)authToken;
    {
        self.initialized = YES;
        [self clearAuthorizationHeader];
        [self setAuthorizationHeaderWithUsername:[userId stringValue] password:authToken];
    }
    
    #pragma mark - Singleton Methods
    
    + (APIClient *)sharedManager {
        static dispatch_once_t pred;
        static APIClient *_s = nil;
    
        dispatch_once(&pred, ^{
            _s = [[self alloc] initWithBaseURL:[NSURL URLWithString:SERVER_ADDR]];
            _s.lock =[NSLock new] ;
        });
    
        [_s.lock lock];
        if (!(_s.initialized)) {
            NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
            NSNumber *userId = @([prefs integerForKey:KEY_USER_ID]);
            NSString *authToken = [prefs stringForKey:KEY_AUTH_TOKEN];
    
            // If still doesn't exist, we need to fetch
            if (userId && authToken) {
                [_s setUserId:userId andAuthToken:authToken];
            } else {
                /*
                 * We can't have obtainCredentials to be a sync operation the OS will kill the thread
                 * Hence we will have to return nil right now.
                 * This means that subsequent calls after asyncObtainCredentials has finished
                 * will have the right credentials.
                 */
                [_s asyncObtainCredentials:^(NSNumber *userId, NSString *authToken){
                    [_s setUserId:userId andAuthToken:authToken];
                }];
                [_s.lock unlock];
                return nil;
            }
        }
        [_s.lock unlock];
    
        return _s;
    }
    
    - (void)asyncObtainCredentials:(void (^)(NSNumber *, NSString *))successBlock {
    
        AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:SERVER_ADDR]];
        NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:[OpenUDID value], @"open_udid", nil];
        NSMutableURLRequest *request = [client requestWithMethod:@"GET" path:@"/get_user" parameters:params];
    
        AFJSONRequestOperation *operation = \
        [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
            ... 
    
            // Do not use sharedManager here cause you can end up in a deadlock
            successBlock(userId, authToken);
    
        } failure:^(NSURLRequest *request , NSURLResponse *response , NSError *error , id JSON) {
            NSLog(@"obtain Credential failed. error:%@ response:%@ JSON:%@",
                  [error localizedDescription], response, JSON);
        }];
    
        [operation start];
        [operation waitUntilFinished];
    }
    

1 个答案:

答案 0 :(得分:2)

您应该在应用程序启动期间检查NSUserDefaults中是否存在这些值。如果它们不存在,请拨打电话从服务器获取它并在屏幕上显示加载覆盖。获取后,您可以继续下一步。

如果你不想使用加载覆盖,你可以在isLoading类中设置一些APIClient标志,并检查是否仍然在提取asyn。因此,无论何时进行服务调用并且需要这些值,您都知道如何根据此标志处理它。获得所需值并存储在NSUserDefaults后,即可继续执行下一步。您可以使用Notifications / Blocks / KVO通知您的viewcontrollers,让他们知道您已获取这些值。