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

Wait for AFNetworking completion block before continuing (i.e. Synchronous)

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

这是我的代码片段,我已经尽可能地简化了它。

我想要 return 成功响应,但我只得到 nil(因为调用块之前的函数 returns)。

- (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,它将在启动时 bootstrap 一个应用程序。 boot strapping 命中一个服务并在本地保存一堆值。然后这些值将用于当前会话并且不会更改。如果值发生变化,用户将获得奇怪的体验,因此我避免这种情况很重要。

如果服务中断了,没关系。我们将使用默认值或从之前的会话中查找一些保存的值,但无论发生什么,我们都不希望用户的体验在会话中发生变化。

(这是一个用于 A/B 测试和试验的引擎 - 如果这对您有帮助 'get' 用例)。

听起来您的应用中有一部分无法 运行,直到启动请求完成。但是也有一部分可以运行(比如发起请求的部分)。给那部分一个 UI 告诉用户我们正忙着准备。不阻塞 UI.

但是如果你必须这样做,并且如果 AFNetworking 不提供请求的阻塞版本(为此向他们致敬),那么你总是可以阻止老式的方式...

- (void)pleaseDontUseThisIdea {

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

    while (thePopeIsCatholic) {}
}

忽略我们一般做不是想做同步网络请求,传统的解决方法是使用信号量:

- (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. 我知道你说过你不想要 "to see some complex dispatch style things",但是出于对其他人的尊重,信号量比正在旋转的 while 循环要好,轮询直到一些值发生变化。

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


为了完整起见,我要指出的是,当有人问 "how to I make some asynchronous method behave synchronously" 时,正确答案总是 "you don't"。相反,您通常采用异步模式,例如将方法更改为采用块参数:

- (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