A nice way to perform a selector on the main thread with two parameters?

8.5k Views Asked by At

I'm searching for a nice way to perform a selector on the main thread with two parameters

I really like using

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

method, except now I have two parameters.

So basically I have a delegate which I need to notify when the image is loaded:

[delegate imageWasLoaded:(UIImage *)image fromURL:(NSString *)URLString;

But the method where I do this might be invoked in the background thread, and the delegate will use this image to update the UI, so this needs to be done in the main thread. So I really want the delegate to be notified in the main thread as well.

So I see one option - I can create a dictionary, this way I have only one object, which contains two parameters I need to pass.

NSDictionary *imageData = [NSDictionary dictionaryWithObjectsAndKeys:image, @"image",     URLString, @"URLstring", nil];
[(NSObject *)delegate performSelectorOnMainThread:@selector(imageWasLoaded:) withObject: imageData waitUntilDone:NO];

But this approach does not seem right to me. Is there more elegant way to do this? Perhaps using NSInvocation? Thanks in advance.

4

There are 4 best solutions below

2
On BEST ANSWER

Since you don't have access to GCD, NSInvocation is probably your best choice here.

NSMethodSignature *sig = [delegate methodSignatureForSelector:selector];
NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig];
[invoke setTarget:delegate]; // argument 0
[invoke setSelector:selector]; // argument 1
[invoke setArgument:&arg1 atIndex:2]; // arguments must be stored in variables
[invoke setArgument:&arg2 atIndex:3];
[invoke retainArguments];
  /* since you're sending this object to another thread, you'll need to tell it
     to retain the arguments you're passing along inside it (unless you pass
     waitUntilDone:YES) since this thread's autorelease pool will likely reap them
     before the main thread invokes the block */

[invoke performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:NO];
0
On

Yes, you've got the right idea: you need to encapsulate all the data you want to pass to the delegate on the main thread into one single object which gets passed along via performSelectorOnMainThread. You can pass it along as a NSDictionary object, or a NSArray object, or some custom Objective C object.

1
On

Following method can also be used:

- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject

As per the docs of this method- Invokes a method of the receiver on the current thread using the default mode after a delay.

3
On

Using an NSDictionary to pass multiple parameters is the right way to go about it in this case.

However, a more modern method is to use GCD and blocks, this way you can send messages to an object directly. Also, it looks as if your delegate method might be doing something UI updates; which you are correctly handling on the main thread. With GCD you can do this easily, and asynchronously like this:

dispatch_async(dispatch_get_main_queue(), ^{
    [delegate imageWasLoaded:yourImage fromURL:yourString;
});

Replace your performSelector:withObject call with this, and you won't have to mess around with changing your method signatures.

Make sure you:

#import <dispatch/dispatch.h>

to bring in GCD support.