9

I have the following method:

- (NSMutableArray *)getElements:(NSString *)theURL;

And I wanted to know if there is a way to call that method using performSelectorOnMainThread so that I can get the return value. So far, I've tried with:

myArray = [object performSelectorOnMainThread:@selector(getElements:)
                                   withObject:url waitUntilDone:YES];

but it doesn't work, since performSelectorOnMainThread returns void. How could I solve this?

The WebMacheter
  • 1,776
  • 1
  • 20
  • 31

4 Answers4

12

Welcome to a multi-threaded environment my friend.

You'll need to store the return value in an instance variable or pass in an object by reference through the withObject parameter:

NSMutableDictionary *myDict;

[object performSelectorOnMainThread:@selector(getElements:)
                                   withObject:&myDict waitUntilDone:YES];

Your method prototype should now look like this:

- (void)getElements:(NSMutableDictionary **)objects;
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
  • 5
    This fails to compile with: "Cannot initialize a parameter of type 'id' with an rvalue of type 'NSMutableDictionary **'" – eodabash Jan 18 '12 at 07:06
3

Had to implement this recently. Great candidate for adding a category to NSObject so that all your objects can do this:

@implementation NSObject (CallSelectorWithObjectOnMainThread)
- (id)resultFromSelectorOnMainThread:(SEL)selector object:(id)object {
  NSMutableDictionary *resultDictionary = [NSMutableDictionary dictionaryWithCapacity:1];
  NSMutableDictionary *callDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:resultDictionary, @"ResultDictionary", NSStringFromSelector(selector), @"Selector", nil];
  if(object) [callDict setValue:object forKey:@"Object"];
  [self performSelectorOnMainThread:@selector(callObject:) withObject:callDict waitUntilDone:YES];
  return [resultDictionary objectForKey:@"Result"];
}

- (void)callObject:(NSMutableDictionary *)info {
  id result;
  SEL selector = NSSelectorFromString([info objectForKey:@"Selector"]);
  id object = [info objectForKey:@"Object"];
  NSMutableDictionary *resultDictionary = [info objectForKey:@"Dictionary"];
  if(object)
    result = [self performSelector:selector withObject:object];
  else
    result = [self performSelector:selector];
  if(result)
    [resultDictionary setValue:result forKey:@"Result"];
}
@end
Colin
  • 2,089
  • 25
  • 34
  • +1 Thanks for this Colin. I found a couple defects after using it: callDict needs to be initialized with NSMutableDictionary and callObject needs to have an NSMutableDictionary method argument. – TPoschel May 30 '13 at 18:29
3

You can't do it directly, because, as you say, that method returns void.

So, you'd have to arrange another way to get a value back, for example by passing an NSDictionary instead of an NSString, and having the method store the result in the dictionary for you.

David Gelhar
  • 27,873
  • 3
  • 67
  • 84
2

I have universal solution for async blocking call with return value for any thread, not only main. This example for main thread like performSelectorOnMainThread:withObject:waitUntilDone:

__block id result = nil;
NSOperationQueue* targetQueue = [NSOperationQueue* mainQueue];
// targetQueue is main thread, but it may be queue on any thread.

[targetQueue addBlockOperation:^{
    // performs on target thread.
    result = [someObject someSelector];
}];

// wait until operations on targetQueue will finished.
[targetQueue waitUntilAllOperationsAreFinished];

// result is ready here.

I used this technique only in ARC environment.

poGUIst
  • 299
  • 4
  • 10