12

How can I make sure that my function is run only on the main thread? It updates UI elements.

Is a function like this considered 'bad'?

-(void)updateSomethingOnMainThread {
    if ( ![[NSThread currentThread] isEqual:[NSThread mainThread]] )
        [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];
    else {
        // Do stuff on main thread
    }
}

I wrote it like this to avoid having a second function, initially I had it like this:

-(void)updateSomethingOnMainThread_real {
    // Do stuff on main thread
}

-(void)updateSomethingOnMainThread {
    [self performSelectorOnMainThread:@selector(updateSomethingOnMainThread_real) withObject:nil waitUntilDone:NO];
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
thelaws
  • 7,991
  • 6
  • 35
  • 54
  • 2
    possible duplicate of [Is circular method calling allowed?](http://stackoverflow.com/questions/7706329/is-circular-method-calling-allowed) – ughoavgfhw Oct 21 '11 at 17:05

4 Answers4

17

As an alternative to ayoy's method-based GCD implementation for guaranteeing execution on the main thread, I use the following GCD-based function in my code (drawn from another answer of mine):

void runOnMainThreadWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

You can then use this helper function anywhere in your code:

runOnMainThreadWithoutDeadlocking(^{
    // Do stuff that needs to be on the main thread
});

This guarantees that the actions taken in the enclosed block will always run on the main thread, no matter which thread calls this. It adds little code and is fairly explicit as to which code needs to be run on the main thread.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • +1 Brad, could you briefly explain the "without deadlocking" name, please? What's the risk, and what techniques would NOT avoid it? Is `performSelectorOnMainThread` with `waitUntilDone` as `NO` not deadlocking, too? – Dan Rosenstark Dec 16 '11 at 21:54
  • 2
    @Yar - I explain this in the answer I link to above: http://stackoverflow.com/a/5226271/19679 . Using a synchronous dispatch to the main queue (analogous to `-performSelectorOnMainThread:` with `waitUntilDone` set to YES) will deadlock if this is called in something already running on the main thread. This surprised me, and led me to create the above helper function. `-performSelectorOnMainThread:` doesn't have this problem, even if `waitUntilDone` is set to YES. Asynchronous dispatches to the main queue don't have the same deadlocking problem. – Brad Larson Dec 16 '11 at 22:23
9

This is fine. You can also use GCD to execute code on the main thread.

Checkout this SO post.

GCD to perform task in main thread

Community
  • 1
  • 1
logancautrell
  • 8,762
  • 3
  • 39
  • 50
6

I wrote this simple #define which I've been using with great success:

#define ensureInMainThread(); if (!NSThread.isMainThread) { [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];    return; }

That way your method, assuming it's parameterless, looks like this

- (void) updateTheThings {
      ensureInMainThread();
      [self.dog setTailWag:YES];
      // etc...
jrouquie
  • 4,315
  • 4
  • 27
  • 43
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • How do you handle parameters? – Moshe Dec 16 '11 at 17:47
  • 2
    @Moshe what parameter? I think the method doesn't need any parameter. :) – Kjuly Dec 16 '11 at 17:50
  • What if I wanted to pass a method with a parameter to a different thread? – Moshe Dec 16 '11 at 18:00
  • @Moshe, this does not handle parameters. However, it could be adjusted to handle them since you've got the `withObject:` piece of the call. You'd just have to work with this on the left side of the define `_X_` and this on the right side `#_X_` Hope that helps. – Dan Rosenstark Dec 16 '11 at 19:31
  • @Moshe - I've reposted a GCD-based helper function as an answer here, which is how I handle guaranteeing something is on the main thread. Using a block as input lets you run arbitrary code here, so I've yanked out all of my `-performSelectorOnMainThread:` calls and replaced them with this. Blocks let you deal with arbitrary numbers of parameters, and I think just make everything easier to read. – Brad Larson Dec 16 '11 at 20:19
4

Alternatively, you can use Grand Central Dispatch API, but it's not very handy:

-(void)updateSomethingOnMainThread {
    void (^doStuff)(void) = ^{
        // stuff to be done
    };

    // this check avoids possible deadlock resulting from
    // calling dispatch_sync() on the same queue as current one
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    if (mainQueue == dispatch_get_current_queue()) {
        // execute code in place
        doStuff();
    } else {
        // dispatch doStuff() to main queue
        dispatch_sync(mainQueue, doStuff);
    }
}

otherwise, if synchronous call isn't needed, you can call dispatch_async() which is much simpler:

-(void)updateSomethingOnMainThread {
    dispatch_async(dispatch_get_main_queue(), ^{
        // do stuff
    });
}
ayoy
  • 3,835
  • 19
  • 20
  • 1
    Be careful with that code, if you call that function on the main thread it will bind up. – NJones Oct 21 '11 at 17:10
  • Oops, right, sorry. So I guess the method should check if the current queue is the main queue and if not, dispatch it and if so, execute the block in place, right? That would make the code more complicated than the non-GCD solution :) – ayoy Oct 21 '11 at 17:16
  • Based on logan's answer, I think what you have is already correct. No check needed. – thelaws Oct 21 '11 at 17:25
  • I was just wondering whether calling `dispatch_sync()` instead of `dispatch_async()` messes up anything. After all, it should be synchronous if you wanted to wait until the block returns. – ayoy Oct 21 '11 at 17:29
  • Got it: _Calling this function and targeting the current queue results in deadlock._ - quote from documentation on `dispatch_sync()` – ayoy Oct 21 '11 at 17:43
  • Regarding the deadlocking issue, I describe this in a little more detail within my answer here: http://stackoverflow.com/questions/5225130/grand-central-dispatch-gcd-vs-performselector-need-a-better-explanation/5226271#5226271 , as well as provide a safe helper function I use to dispatch blocks on the main queue. – Brad Larson Nov 18 '11 at 17:29