15

dispatch_sync() has trap

solve a framework-in-framework code signing issues

dispatch_sync() has trap

Per documentation:

As an optimization, this function invokes the block on the current thread when possible.

This is a trap if you try to feed a different queue and call this function upon main queue, it will actually stack all frames into the target queue from main queue, like this:

dispatch_sync(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
    [RestAPI registerDevice:^{
 
    }];
});

notice that the frames that belong to main queue now is stacked on the background queue. This gets messy when you try to add a semaphore inside the block, for example:

+ (void)registerDevice:(void (^)(void))completionBlock {
    while (true){
        __block BOOL success = NO;
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        [[AFHTTPSessionManager sharedInstance] POST:@"register"
                                         parameters:nil
                                            success:^(NSURLSessionDataTask *task, id responseObject) {
                                                success = YES;
                                                DDLog(@"[REGISTER SUCCESS] %@", responseObject);
                                                dispatch_semaphore_signal(semaphore);
                                            } failure:^(NSURLSessionDataTask *task, NSError *error) {
                                                DDLog(@"[REGISTER FAILED] %@", error);
                                                dispatch_semaphore_signal(semaphore);
                                            }];
        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2 * MI_HTTP_TIMEOUT * NSEC_PER_SEC);
        dispatch_semaphore_wait(semaphore, timeout);
        if (success) {
            completionBlock();
            break;
        } else {
            [NSThread sleepForTimeInterval:REGISTER_RESEND_INTERVAL];
        }
    }
}

In this case, despite the weird “sync” operation by the semaphore inside the original dispatch_sync() , the block will use AFNetworking session manager to post something. There is a tricky property called completionQueue inside the manager, which default is the main queue. Now when the request gets its response, AFNetworking will be called upon:

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
    if (self.completionHandler) {
        self.completionHandler(task.response, responseObject, serializationError);
    }
...
});

Notice the main queue has been stacked onto the background queue, though it’s saying background queue, it’s actually the main queue itself. So the semaphore is actually locked on the main queue. When completionQueue is nil here, it will try to get the main queue and execute the block, which will eventually become dead lock, as our main queue is blocked by the semaphore, it will run time out and repeat over and over again. If we remove the while loop here, the semaphore will time out and we will have to sleep another REGISTER_RESEND_INTERVAL seconds, and then the post completion handler will be called on main queue again.

To sum up, be careful with the dispatch_sync() as it can be optimized to invoke the block on the current thread when possible.