提供带有目标回调队列的异步和同步 API
Offering both asynchronous and synchronous API with a target callback queue
我正在写一个网络API。由于对 NSURLSession
的底层调用始终是异步的,因此我默认提供异步 API:
- (void) callBackendServerWithCompletion: (dispatch_block_t) completion;
提供此 API 的同步版本也非常方便,例如简化 Xcode 游乐场中的代码测试。同步调用按照异步调用来写:
- (void) callBackendSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self callBackendServerWithCompletion:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
这很好用。
现在我想添加一个额外的便利功能,一个默认的调度队列来调用完成块。此回调队列默认为 UI 队列,因此此 API 的消费者不必一直 dispatch_async(dispatch_get_main_queue(), ^{…})
:
// This:
[webservice callBackendServerWithCompletion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
}];
// Would be replaced with this:
[webservice callBackendServerWithCompletion:^{
// Guaranteed to run on the main queue
[self updateUI];
}];
这很容易做到,但现在我在主队列上调用同步方法时遇到了死锁:
-callBackendSynchronously
调用 -callBackendServerWithCompletion
并等待信号量。
- 异步方法处理网络请求并在主队列上调度回调。
- 由于主队列已经在等待信号量,代码死锁。
提供所有三个功能的简单方法是什么,即。同步和异步 API 方法和默认回调队列?
添加 callBackendServerWithCompletion
的私有重载版本,接受调度队列。在 callBackendSynchronously
中,使用自定义后台队列调用这个新的重载方法。
最后,在您原来的callBackendServerWithCompletion
方法中调用重载版本,将默认队列作为参数传递。
一个简单的解决方法是不将回调队列添加为 属性,而是作为异步调用的参数:
/// Guaranteed to call the completion on the main queue
- (void) callBackendServerWithCompletion: (dispatch_block_t) completion;
/// Pick your own callback queue
- (void) callBackendServerWithTargetQueue: (dispatch_queue_t) callbackQueue completion: (dispatch_block_t) completion;
然后同步方法可以为回调指定一个全局队列,打破死锁,因为信号量是从另一个线程发出信号的:
- (void) callBackendSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self callBackendServerWithCallbackQueue:dispatch_get_global_queue(0, 0) completion:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
我还不确定是否有一些缺点。
像这样实现同步 API 会导致 QOS 和重要性继承出现问题。我强烈建议您改变您的范式,尽可能避免使用信号量。假设你有一个序列化你的操作的操作队列,你可以这样做:
-(void)doItAsyncWithCompletionHandler:(nullable void (^)(NSError * _Nullable error)completionHandler
{
[self doItAsyncWithCompletionQueue:nil completionHandler:completionHandler];
}
-(void)doItAsyncWithCompletionQueue:(nullable dispatch_queue_t)completionQueue
completionHandler:(nullable void (^)(NSError * _Nullable error)completionHandler
{
if (!completionQueue) {
completionQueue = dispatch_get_global_queue(qos_class_self(), 0);
}
completionHandler = completionHandler.copy;
dispatch_async(self.operationQueue, ^{
NSError *error;
BOOL success = [self _onOperationQueueDoItWithError:&error];
NSAssert((success && !error) || (!success && error), @"API Contract violation in -_onOperationQueueDoItWithError:");
if (completionHandler) {
dispatch_async(completionQueue, ^{
completionHandler(error);
});
}
});
}
-(BOOL)doItSyncWithError:(NSError * __autoreleasing _Nullable * _Nullable)error
{
__block BOOL success;
dispatch_sync(self.operationQueue, ^{
success = [self _onOperationQueueDoItWithError:error];
});
return success;
}
-(BOOL)_onOperationQueueDoItWithError:(NSError * __autoreleasing _Nullable * _Nullable)error
{
dispatch_assert_queue(self.operationQueue);
...
}
我正在写一个网络API。由于对 NSURLSession
的底层调用始终是异步的,因此我默认提供异步 API:
- (void) callBackendServerWithCompletion: (dispatch_block_t) completion;
提供此 API 的同步版本也非常方便,例如简化 Xcode 游乐场中的代码测试。同步调用按照异步调用来写:
- (void) callBackendSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self callBackendServerWithCompletion:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
这很好用。
现在我想添加一个额外的便利功能,一个默认的调度队列来调用完成块。此回调队列默认为 UI 队列,因此此 API 的消费者不必一直 dispatch_async(dispatch_get_main_queue(), ^{…})
:
// This:
[webservice callBackendServerWithCompletion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
}];
// Would be replaced with this:
[webservice callBackendServerWithCompletion:^{
// Guaranteed to run on the main queue
[self updateUI];
}];
这很容易做到,但现在我在主队列上调用同步方法时遇到了死锁:
-callBackendSynchronously
调用-callBackendServerWithCompletion
并等待信号量。- 异步方法处理网络请求并在主队列上调度回调。
- 由于主队列已经在等待信号量,代码死锁。
提供所有三个功能的简单方法是什么,即。同步和异步 API 方法和默认回调队列?
添加 callBackendServerWithCompletion
的私有重载版本,接受调度队列。在 callBackendSynchronously
中,使用自定义后台队列调用这个新的重载方法。
最后,在您原来的callBackendServerWithCompletion
方法中调用重载版本,将默认队列作为参数传递。
一个简单的解决方法是不将回调队列添加为 属性,而是作为异步调用的参数:
/// Guaranteed to call the completion on the main queue
- (void) callBackendServerWithCompletion: (dispatch_block_t) completion;
/// Pick your own callback queue
- (void) callBackendServerWithTargetQueue: (dispatch_queue_t) callbackQueue completion: (dispatch_block_t) completion;
然后同步方法可以为回调指定一个全局队列,打破死锁,因为信号量是从另一个线程发出信号的:
- (void) callBackendSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self callBackendServerWithCallbackQueue:dispatch_get_global_queue(0, 0) completion:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
我还不确定是否有一些缺点。
像这样实现同步 API 会导致 QOS 和重要性继承出现问题。我强烈建议您改变您的范式,尽可能避免使用信号量。假设你有一个序列化你的操作的操作队列,你可以这样做:
-(void)doItAsyncWithCompletionHandler:(nullable void (^)(NSError * _Nullable error)completionHandler
{
[self doItAsyncWithCompletionQueue:nil completionHandler:completionHandler];
}
-(void)doItAsyncWithCompletionQueue:(nullable dispatch_queue_t)completionQueue
completionHandler:(nullable void (^)(NSError * _Nullable error)completionHandler
{
if (!completionQueue) {
completionQueue = dispatch_get_global_queue(qos_class_self(), 0);
}
completionHandler = completionHandler.copy;
dispatch_async(self.operationQueue, ^{
NSError *error;
BOOL success = [self _onOperationQueueDoItWithError:&error];
NSAssert((success && !error) || (!success && error), @"API Contract violation in -_onOperationQueueDoItWithError:");
if (completionHandler) {
dispatch_async(completionQueue, ^{
completionHandler(error);
});
}
});
}
-(BOOL)doItSyncWithError:(NSError * __autoreleasing _Nullable * _Nullable)error
{
__block BOOL success;
dispatch_sync(self.operationQueue, ^{
success = [self _onOperationQueueDoItWithError:error];
});
return success;
}
-(BOOL)_onOperationQueueDoItWithError:(NSError * __autoreleasing _Nullable * _Nullable)error
{
dispatch_assert_queue(self.operationQueue);
...
}