简体   繁体   中英

Macro to run method on main thread

I want an easy way to drop code in the beginning of a method that will force the method to only be run on the main thread (because the method updates UI elements).

Currently, I have something like:

 if (![NSThread isMainThread]){
    [self performSelectorOnMainThread:_cmd withObject:_results waitUntilDone:NO];
    return;
}

but I want a way to include this in a macro without me having to enter the arguments for the method. It seems like there should be some way to iterate over the list of parameters passed to the current method and create an NSInvocation or similar. Any thoughts?

Will this work?

#define dispatch_main($block) (dispatch_get_current_queue() == dispatch_get_main_queue() ? $block() : dispatch_sync(dispatch_get_main_queue(), $block))

This will also work if you call it from the main thread too, a bonus. If you need asynchronous calling, just use dispatch_async instead of dispatch_sync .

Rather than trying to re-call your method on a different thread, I'd suggest using dispatch_sync() and dispatch_get_main_queue() to ensure that just the sensitive code is on the main thread. This can easily be wrapped in a function, as in Brad Larson's answer to "GCD to perform task in main thread" .

His procedure is essentially the same as what you already have, the difference being that the code is put into a block and either called or enqueued as appropriate:

if ([NSThread isMainThread])
{
    blockContainingUICode();
}
else
{
    dispatch_sync(dispatch_get_main_queue(), blockContainingUICode);
}

Which could also be translated into a macro without a lot of trouble, if you so desire.

Creating the block itself doesn't require much of a change. If your UI code looks like this:

[[self textLabel] setText:name];

[[self detailTextLabel] setText:formattedDollarValue];

[[self imageView] setImage:thumbnail];

Putting it in a block to be enqueued just looks like this:

dispatch_block_t blockContainingUICode = ^{

    [[self textLabel] setText:mainText];

    [[self detailTextLabel] setText:detailText];

    [[self imageView] setImage:thumbnail];
};

The only way I know to create a dynamic NSInvocation from a method like that would require the arguments of your method to be a va_list.

You would need to be able to get the params of the current method as an array (so that you could loop the array and add the params to the NSInvocation) and I am not sure that this is possible (I don't think it is).

This will work if called from main thread or background thread using NSThread conditional ternary.


Synchronous:

#define dispatch_main(__block) ([NSThread isMainThread] ? __block() : dispatch_sync(dispatch_get_main_queue(), __block))

Asynchronous:

#define dispatch_main(__block) ([NSThread isMainThread] ? __block() : dispatch_async(dispatch_get_main_queue(), __block))

To use:

-(void)method {
    dispatch_main(^{
        //contents of method here.
    });
}

Attribution: Inspired by Richard Ross's Answer

I think this is what you are looking for:

- (void)someMethod
{
    // Make sure the code will run on main thread

    if (! [NSThread isMainThread])
    {
        [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:YES];

        return;
    }

    // Do some work on the main thread
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM