Keeping it to the main thread

You probably know that UIKit is not thread safe, so for instance if you want to display an alert following a network request you need to make sure that it runs on the main thread. Most developers will resort to dispatch_async to help them with that.

- (void)showErrorMessage
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [[[UIAlertView alloc] initWithTitle:nil message:@"Something's wrong" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show];
    });
}

However, the problem with that is if you have a UIKit method that is accessed both from background threads and from main. You don’t want to call dispatch_async on main, from main because it introduces a totally unnecessary delay of one run loop before your code runs and could cause all sorts of timing issues at a later stage.

A naive solution is to create two implementations of your UIKit method, one with dispatch_async and one without, or to wrap your call to your UIKit method itself in dispatch_async. But that’s ugly and you are bound to forget it sooner or later.

- (void)showErrorMessage
{
    [[[UIAlertView alloc] initWithTitle:nil message:@"Something's wrong" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show];
}

- (void)showErrorMessageOnMainThread
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [[[UIAlertView alloc] initWithTitle:nil message:@"Something's wrong" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show];
    });
}

Enter dispatch_main(block). Its a macro that takes a block of code and checks which thread its being called on. If the current thread is already main, the block of code is run inline. If its not, a dispatch_sync is performed. It’s safe to use sync without the risk of a deadlock since we’ve already verified that we are in fact not on the main thread.

Place the following code in you prefix header file (Swift 3.0 below):

#define dispatch_main(block)\
if (NSThread.isMainThread) {\
    block();\
} else {\
    dispatch_sync(dispatch_get_main_queue(), block);\
}

Then implement your showErrorMessage method like so:

- (void)showErrorMessage
{
    dispatch_main(^{
        [[[UIAlertView alloc] initWithTitle:nil message:@"Something's wrong" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show];
    });
}

Now you can rest assured that each call to showErrorMessage will run on main, without any unnecessary delays!

Bonus version for Swift 3.0:

func dispatch_main(_ block: () -> Void) {
    if Thread.isMainThread {
        block()
    } else {
        DispatchQueue.main.sync {
            block()
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *