在继续之前等待AFNetworking完成块(即同步)

时间:2015-03-10 23:44:38

标签: objective-c afnetworking-2

我有一个用于AFNetworking同步行为的用例(详情如下)。我怎样才能做到这一点?

这是我的代码段,我尽可能简化了。

我想返回成功响应,但我只得到nil(因为函数在调用块之前返回)。

- (id)sendForUrl:(NSURL *)url {
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    __block id response;
    [manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
        response = responseObject;
        NSLog(@"JSON: %@", responseObject);
    } failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Error: %@", error);
    }];

    return response;
}

详情 所以我需要这个同步行为的原因是因为我正在构建一个将在启动时引导应用程序的pod。引导捆绑命中服务并在本地保存一堆值。然后,这些值将用于当前会话,而不会更改。如果值发生变化,用户将获得奇怪的体验,因此重要的是我要避免这种情况。

如果服务中断,那没关系。我们将使用默认值或查找上一个会话中的某些已保存值,但无论发生什么,我们都不希望用户的体验在会话中更改。

(这是A / B测试和实验的引擎 - 如果这有助于你“获得”用例)。

2 个答案:

答案 0 :(得分:11)

忽略我们通常想要发出同步网络请求的事实,传统的解决方案是使用信号量:

- (id)sendForUrl:(NSURL *)url {
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

    manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    __block id response;

    [manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
        response = responseObject;
        NSLog(@"JSON: %@", responseObject);
        dispatch_semaphore_signal(semaphore);
    } failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Error: %@", error);
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return response;
}

这里有两个问题:

  1. 我知道你说你不想“看到一些复杂的派遣风格的东西”,但是在适当尊重他人的情况下,信号量优于旋转的while循环,轮询直到一些价值变化。

  2. 注意,假设您从主线程调用此方法,则必须将completionQueue设置为其他后台队列。默认情况下,AFHTTPRequestOperationManager将使用主队列来完成这些完成块。如果你阻止该线程等到完成阻止在同一个线程上运行,你就会陷入僵局(即你的应用程序将冻结)。


  3. 为了完整起见,我会指出,当有人问“我如何让一些异步方法同步行为”时,正确的答案是“你没有”。相反,您通常采用异步模式,例如更改方法以获取块参数:

    - (void)sendForUrl:(NSURL *)url completionHandler:(void (^)(id responseObject, NSError *error))completionHandler {
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    
        manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        [manager GET:url.absoluteString parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
            if (completionHandler) {
                completionHandler(responseObject, nil);
            }
        } failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
            if (completionHandler) {
                completionHandler(nil, error);
            }
        }];
    }
    

    然后你会这样调用它,提供一个完成处理程序块:

    [object sendForURL:url completionHandler:^(id responseObject, NSError *error) {
        // use responseObject and/or error here
    }];
    
    // don't use them here
    

答案 1 :(得分:2)

听起来你的应用程序中有一部分在启动请求完成之前无法运行。但是还有一部分可以运行(比如启动请求的部分)。为该部分提供一个UI,告诉用户我们正忙着做好准备。没有阻止UI。

但是如果你必须这样做,并且如果AFNetworking没有提供请求的阻止版本(对此他们的称赞),那么你总是可以阻止旧式的方式......

- (void)pleaseDontUseThisIdea {

    __block BOOL thePopeIsCatholic = YES;
    [manager GET: ....^{
        // ...
        thePopeIsCatholic = NO;
    }];

    while (thePopeIsCatholic) {}
}