Ios 提供异步和同步API以及目标回调队列
我正在编写一个网络API。由于对Ios 提供异步和同步API以及目标回调队列,ios,multithreading,macos,grand-central-dispatch,Ios,Multithreading,Macos,Grand Central Dispatch,我正在编写一个网络API。由于对NSURLSession的底层调用始终是异步的,因此默认情况下我提供了一个异步API: - (void) callBackendServerWithCompletion: (dispatch_block_t) completion; 提供此API的同步版本也非常方便,例如简化在Xcode操场中测试代码。同步调用是按照异步调用编写的: - (void) callBackendSynchronously { dispatch_semaphore_t sema
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\u async(dispatch\u get\u main\u 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主要用于测试和游乐场,所以这不是一个大问题,但无论如何还是要感谢您!我不知道这件事。