iOS – performSelectorOnMainThread with Multiple Objects

John WordsworthiOS Development4 Comments

I recently ran into the situation where I wanted to call NSObject’s performSelectorOnMainThread with multiple arguments. While it wasn’t massively difficult, it was a bit more fiddly than I first expected – you have to get your hands dirty with NSInvocation. I’ve built a category that adds an additional method to NSObject which will allow you to call a performSelectorOnMainThread method with any number of objects as parameters. Feel free to use it in your own projects!

If you’re new to Objective-C, categories allows you to add additional methods to existing classes – without having to have access to the source code for that class. In this instance, we will add a new method to NSObject (and therefore, every object that inherits from NSObject) that will act much like the already existing performSelectorOnMainThread method, only it will accept any number of objects as input. You can read more about categories in this very helpful Objective-C guide.

Our code consists of two parts, the header file – which will define the extra method for NSObject, and the .m file, as per usual. The header file is simple (excuse my long file name, I like to be precise);

NSObject+PerformSelectorOnMainThreadMultipleArgs.h

#import <Foundation/Foundation.h>

@interface NSObject (PerformSelectorOnMainThreadMultipleArgs)

-(void)performSelectorOnMainThread:(SEL)selector waitUntilDone:(BOOL)wait withObjects:(NSObject *)object, ... NS_REQUIRES_NIL_TERMINATION;

@end

Nothing really out of the ordinary here. Just notice that using the (parathesis) after NSObject tells Objective-C that we’re making a category on NSObject. It’s also worth noting that the ‘with objects’┬áparameter for the method we’ve created has a comma and 3 ellipsis after the first parameter’s variable name. This is stating that we’re expecting a list of parameters. The NS_REQUIRES_NIL_TERMINATION states that, much like some of the NSMutableArray instance methods, we’re expecting a list which is terminated by a nil value. Now, onto the code for this method;

NSObject+PerformSelectorOnMainThreadMultipleArgs.m

#import "NSObject+PerformSelectorOnMainThreadMultipleArgs.h"

@implementation NSObject (PerformSelectorOnMainThreadMultipleArgs)

-(void)performSelectorOnMainThread:(SEL)selector waitUntilDone:(BOOL)wait withObjects:(NSObject *)firstObject, ... 
{
    // First attempt to create the method signature with the provided selector.
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
	
    if ( !signature ) {
        NSLog(@"NSObject: Method signature could not be created.");
        return;
    }

    // Next we create the invocation that will actually call the required selector.
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:selector];

    // Now add arguments from the variable list of objects (nil terminated).
    va_list args;
    va_start(args, firstObject);
    int nextArgIndex = 2;
	
    for (NSObject *object = firstObject; object != nil; object = va_arg(args, NSObject*))
    {
        if ( object != [NSNull null] ) 
        {
            [invocation setArgument:&object atIndex:nextArgIndex];
        } 
	
        nextArgIndex++;
    }	
	
    va_end(args);
	
    [invocation retainArguments];
    [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:wait];
}

@end

Now, you probably came here just looking for some code – so I won’t put too much tutorial in here. First off, let me just say that I’ve switched around the parameters for the method from the standard order so that the variable length list is the last parameter. Next up, I’ll just touch on the two main elements of the code.

First of all, this code relies on NSInvocation to do the leg work. NSInvocation represents the action of calling a method, but wraps it up in an object – so that you can do a number of things with it. In this case, it means that we can build the NSInvocation from the list of input objects that we’ve received, and then ensure that the NSInvocation object calls it’s own invoke method (calling the selector on the calling item) on the main thread. This means that we can lever the regular performSelectorOnMainThread functionality without having to do too much work on our own.

Secondly, the va_list is what allows us to pass the method a variable number of arguments. We keep popping objects off the argument list (in the for loop) until we reach a nil object. However, as we might also want to pass nil as an argument to our selector, I’ve made it such that you can pass NSNull which will then be interpreted as a nil argument in the final selector call. Here we simply pop the arguments from the withObjects parameter to the method and add them as arguments to the invocation. Note that we start at argument 2 for the invocation as NSInvocation’s arguments 0 and 1 are used for other purposes. So argument 2 actually corresponds to the first argument for the selector you’ve chosen.

That’s about it really – if you have any questions, please feel free to ask away in the comments section below. Otherwise, enjoy the code!

John WordsworthiOS – performSelectorOnMainThread with Multiple Objects

4 Comments on “iOS – performSelectorOnMainThread with Multiple Objects”

  1. bob

    why [NSNull null] as the sentinal? why not nil, as [NSArray arrayWithObjects:] and [NSDictionary dictionaryWithObjectsAndKeys:], etc. use?

    Or, to make it even more general (because you might actually want to pass nil to a method), why not simply get the count of arguments from the method signature?

    1. John Wordsworth

      Hi Bob,

      Thanks for your comments on this code. It’s been a while since I’ve used this specific code as I’ve not returned to the project I wrote it for in a couple of months. However, I believe the following to be true;

      1. The above code uses ‘NS_REQUIRES_NIL_TERMINATION’, which means that we are actually using ‘nil’ as the sentinel in this case, which is the same as when you are using [NSArray arrayWithObjects:] and [NSDictionary dictionaryWithObjectsAndKeys:].

      2. The above case has been made general by allowing you to pass [NSNull null] objects as part of the list (as the list is terminated by nil, but not NSNull terminated). If the method receives an [NSNull null] object it is intended that this will be treated as something which should be passed as ‘nil’ to the resulting method. We achieve this by simply not setting an argument at the relevant index in the invocation, which is the same as passing a ‘nil’ argument. I agree that this might not be the best way of doing things, but I thought it was relatively elegant.

      I’ll have a quick check over the above code at the weekend and I’ll try to rewrite parts of the above tutorial to make it a bit clearer over the upcoming weekend. I believe that a usage example will make it all very clear!

      Thanks for stopping by and posting your comment. I always find it interesting to question / try to improve the code I’ve written as it helps to keep me sharp!

  2. Amir Akram

    John Wordsworth: I am biggest fan of your blog and your articles. Really Learn alot from you thank soo much :)
    can you add me Gmail or Facebook ? for proper interaction ?

    1. John Wordsworth

      Hi Amir, I’ve been a bit slow in keeping up with comments lately! Thanks for the kind words – I have just ‘subscribed’ to you on Facebook (I see the terminology has changed again!).

      All the best.

Leave a Reply

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