I have a very weird situation in GCD in my code, and here's a minimum reproducible code:
- (void)runMyCode {
__block BOOL completed = NO;
[self runSomeAsyncAPI:^{
completed = YES;
}];
while (!completed) {
[NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_main_queue(), ^{
[self runMyCode];
});
}
The completion block is not called at all. But if I don't dispatch_async, it works. This is strange.
The problem is that it is dispatching runMyCode
to a serial queue, spinning until complete
is set. Thus, nothing else dispatched to that same serial queue can run until the code dispatched via dispatch_async
finishes. Sure, the run loop can process events, but that serial queue is tied up. The dispatch_after
(which will set complete
) is unable to run on that same serial queue.
There are a number of ways of solving this:
The spinning on the run loop is a technique that predates GCD and is an anti-pattern, nowadays. The best solution would be to retire that and adopt asynchronous patterns. We can probably offer some reasonable, contemporary alternatives if we understood why you are spinning.
You can use different queues for the initial dispatch_async
and the subsequent dispatch_after
.
You can use a concurrent queue (either create your own or use a global queue).