Ios 提供异步和同步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

我正在编写一个网络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\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主要用于测试和游乐场,所以这不是一个大问题,但无论如何还是要感谢您!我不知道这件事。