If your completion block is also called on the Main Thread, it might be difficult to achieve this, because before the completion block can execute, your method need to return. You should change implementation of the asynchronous method to:
- Be synchronous.
or
- Use other thread/queue for completion. Then you can use Dispatch Semaphores for waiting. You initialize a semaphore with value
0
, then call wait
on main thread and signal
in completion.
In any case, blocking Main Thread is very bad idea in GUI applications, but that wasn't part of your question. Blocking Main Thread may be required in tests, in command-line tools, or other special cases. In that case, read further:
How to wait for Main Thread callback on the Main Thread:
There is a way to do it, but could have unexpected consequences. Proceed with caution!
Main Thread is special. It runs +[NSRunLoop mainRunLoop]
which handles also +[NSOperationQueue mainQueue]
and dispatch_get_main_queue()
. All operations or blocks dispatched to these queues will be executed within the Main Run Loop. This means, that the methods may take any approach to scheduling the completion block, this should work in all those cases. Here it is:
__block BOOL isRunLoopNested = NO;
__block BOOL isOperationCompleted = NO;
NSLog(@"Start");
[self performOperationWithCompletionOnMainQueue:^{
NSLog(@"Completed!");
isOperationCompleted = YES;
if (isRunLoopNested) {
CFRunLoopStop(CFRunLoopGetCurrent()); // CFRunLoopRun() returns
}
}];
if ( ! isOperationCompleted) {
isRunLoopNested = YES;
NSLog(@"Waiting...");
CFRunLoopRun(); // Magic!
isRunLoopNested = NO;
}
NSLog(@"Continue");
Those two booleans are to ensure consistency in case of the block finished synchronously immediately.
In case the -performOperationWithCompletionOnMainQueue:
is asynchronous, the output would be:
Start
Waiting...
Completed!
Continue
In case the method is synchronous, the output would be:
Start
Completed!
Continue
What is the Magic? Calling CFRunLoopRun()
doesn’t return immediately, but only when CFRunLoopStop()
is called. This code is on Main RunLoop so running the Main RunLoop again will resume execution of all scheduled block, timers, sockets and so on.
Warning: The possible problem is, that all other scheduled timers and block will be executed in meantime. Also, if the completion block is never called, your code will never reach Continue
log.
You could wrap this logic in an object, that would make easier to use this pattern repeatedy:
@interface MYRunLoopSemaphore : NSObject
- (BOOL)wait;
- (BOOL)signal;
@end
So the code would be simplified to this:
MYRunLoopSemaphore *semaphore = [MYRunLoopSemaphore new];
[self performOperationWithCompletionOnMainQueue:^{
[semaphore signal];
}];
[semaphore wait];