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

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.

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.

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];

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.

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.

Related

passing 2 arguments for #selector in its simplest way

SEL twoArgumentSelector = #selector(methodWithTwoArguments:and:);
[newsButton addTarget:self action:#selector(twoArgumentSelector) forControlEvents:UIControlEventTouchUpInside];
-(void)methodWIthTwoArguments:(id)argumentOne and:(id)argumentTwo;
I have seen some examples that let you use two arguments in a selector. How would any do that in the above code? ty in advance.
#selector(twoArgumentSelector:and:)
although I'm not sure how you would send two arguments with a control event...
edit:
you know that the selector isn't actually calling the method, so you can't pass the arguments with the selector. It is basically just the name for a block of code (the method). Read this. A better solution would be to have the control event call a separate method which could then determine the arguments to send to the method with 2 parameters.
UIControls events by will only send a reference to themselves if their target selector allows for one argument. This is all you get. UIButton is one such UIControl subclass.
- (void)buttonAction:(id)sender; //(reference to button)
The easiest way to accomplish what you want is to make another method on your button target (in this case self) that calls out to your two argument selector.
[newsButton addTarget:self action:#selector(buttonAction:) forControlEvents:...];
- (void)buttonAction:(id)sender
{
[self methodwithTwoArguements:sender and:otherObject];
}
This could also be solved with a UIButton subclass, but depending on what your second argument needs to be, this is the simplest way.
Unless you are subclassing the object, there is no need for this because the argument to UIControlEvents is the id of the instance sending the event. You can get all the information you could possibly need from that instance.
What you're looking for is NSInvocation.
Here's an example:
SEL mySelector; // 2 parameter selector.
// ...
NSMethodSignature *signature = [[MyClass class] instanceMethodSignatureForSelector:mySelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:myTarget];
[invocation setArgument:arg1 atIndex:0];
[invocation setArgument:arg2 atIndex:1];
[invocation invoke];
This will do exactly what you want.
It's worth noting, however, that the control event you are binding to isn't going to pass anything in for a second parameter. This will only work if, by contract, the object that is calling back actually has a second parameter to pass in.

iOS performSelectorOnMainThread with multiple arguments

I would like to perform a selector on the main thread from another thread, but the selector has multiple arguments, similar to this:
-(void) doSomethingWith:(int) a b:(float)b c:(float)c d:(float)d e:(float)e {
//...
}
How can I get this working with performSelectorOnMainThread: withObject: waitUntilDone:?
EDIT
I would like to explain why i need this.
I'm working with UIImageViews on the main thread, and I make the calculations for them on another thread. I use a lot of calculations so if i make everything on the main thread, the app lags. I know that UI elements can only be manipulated on the main thread, this is why i would like it to work this way, so the main thread can listen to touch events without lags.
When you're using iOS >= 4, you'd do this instead:
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething:1 b:2 c:3 d:4 e:5];
});
That's like doing waitUntilDone:NO. If you want to wait until the method is finished, use dispatch_sync instead.
You'll need to use a NSInvocation
Create the object, set the target, selector and arguments.
Then, use
[ invocationObject performSelectorOnMainThread: #selector( invoke ) withObject: nil, waitUntilDone: NO ];
you can pass one object of NSDictionary/NSArray type having required arguments.
and accept the same type of object in your function.
then, decompose the values and proceed with processing.
you have to use NSNumber for numeric values for adding them to NSarray/NSDictionary and later on in your function, you can convert them back with intValue/floatValue etc
best of buck.

NSThread reading parent's ivars?

I detach a new NSThread withObject:self so that the thread can callback the main thread as a delegate. However, I also need the new thread to be able to read some values in the parent. With NSThread, I can only pass one object withObject, and I'm using that to pass self because of the delegate methods. Is there a way my new thread can read values from it's parent? Perhaps through the self object that is passed to it?
Here's where I launch the thread:
MulticastDaemon* multicastDaemon = [[MulticastDaemon alloc] init];
[NSThread detachNewThreadSelector:#selector(doWorkWithDelegate:)
toTarget:multicastDaemon
withObject:self];
I want to pass a multicast IP address and port number to the daemon, so he knows what to listen on, but I'm not sure how to get those values to multicastDaemon.
How can multicastDaemon access those values?
Yes, you can access the variables by making them properties and then doing something like this (you don't say what the class is that this call is made from, so I've called it MyClass):
#implementation MulticastDaemon
-(void) doWorkWithDelegate:(MyClass*) cls
{
cls.value1 = 12;
...
}
...
#end
EDIT: Corrected implementation.
You'd better use the subclass of NSOperation and then add it to the NSOperationQueue. You can add any additional parameters to that operation subclass.
There is also another advantage of NSOperation over NSThread. NSOperation and NSOperationQueue are build on top of the GCD and threading is far more optimal then NSThread.
But you can also simply add some properties to your MulticastDaemon.
You can change your MulticastDaemon's interface slightly so that you set the delegate before creating the new thread. Then you free up the withObject: slot to pass something else along. This avoids accessing variables across threads.
Either:
MulticastDaemon* multicastDaemon = [[MulticastDaemon alloc] initWithDelegate:self];
[NSThread detachNewThreadSelector:#selector(doWorkWithInformation:)
toTarget:multicastDaemon
withObject:operatingInfo];
Or
MulticastDaemon* multicastDaemon = [[MulticastDaemon alloc] init];
[multicastDaemon setDelegate:self];
Otherwise, you'll have to create a method that the daemon can call on its delegate that gathers and packages up the information to pass back. In that case, you'll probably have to start worrying about thread safety.

How to use native C types with performSelectorOnMainThread:?

I'd like to call (void)setDoubleValue:(double)value using performSelectorOnMainThread:.
What I thought would work is:
NSNumber *progress = [NSNumber numberWithDouble:50.0];
[progressIndicator performSelectorOnMainThread:#selector(setDoubleValue:)
withObject:progress
waitUntilDone:NO];
Didn't work.
Then I implemented a helper method:
- (void)updateProgressIndicator:(NSNumber *)progress
{
[progressIndicator setDoubleValue:[progress doubleValue]];
}
Works, but not really clean.
After that I tried it with NSInvocation.
NSInvocation *setDoubleInvocation;;
SEL selector = #selector(setDoubleValue:);
NSMethodSignature *signature;
signature = [progressIndicator methodSignatureForSelector:selector];
setDoubleInvocation = [NSInvocation invocationWithMethodSignature:signature];
[setDoubleInvocation setSelector:selector];
[setDoubleInvocation setTarget:progressIndicator];
double progress = 50.0;
[setDoubleInvocation setArgument:&progress atIndex:2];
[setDoubleInvocation performSelectorOnMainThread:#selector(invoke)
withObject:nil
waitUntilDone:NO];
This solution works, but it uses a lot of code and is quite slow. (Even if I store the invocation.)
Is there any other way?
If you are on Snow Leopard, you can use Blocks:
dispatch_async(dispatch_get_main_queue(), ^{
[progressIndicator setDoubleValue: 50.0];
});
you'll need to write a custom un-boxing method to wrap setDoubleValue:.
- (void) setDoubleValueAsNumber: (NSNumber *) number {
[self setDoubleValue: [number doubleValue]];
}
Dave Dribin has a solution for this that takes the shape of a category on NSObject. His category wraps the method call in an NSInvocation and invokes that on the main thread. This way, you can use whatever method interface you like, including primitive types for your arguments.
The Amber framework also has a category on NSObject that adds a main thread proxy, where any messages sent to that proxy are executed on the main thread.
This blog post: http://www.cimgf.com/2008/03/01/does-objective-c-perform-autoboxing-on-primitives/ points out that while Cocoa won't autobox primitives, it will unbox them automatically. So numbers and BOOLs at least can be passed in as an NSNumber class, and the called function will automatically unbox it. I've been playing with using a proxy object (Uli's UKMainThreadProxy*) which works quite well, although I'm sure it has it's limitations like anything else.
http://zathras.de/programming/cocoa/UKKQueue.zip/UKKQueue/UKMainThreadProxy.m

ObjC delegate methods never gets called

I am creating instances of a class FlickrImage parsing a Flickr API photos response. The class has a method getLocation that does another API call to get the geolocation:
NSLog(#"getting location for %i",self.ID);
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
OFFlickrAPIRequest *flickrAPIRequest = [[OFFlickrAPIRequest alloc] initWithAPIContext[appDelegate sharedDelegate].flickrAPIContext];
[flickrAPIRequest setDelegate:self];
NSString *flickrAPIMethodToCall = #"flickr.photos.geo.getLocation";
NSDictionary *requestArguments = [[NSDictionary alloc] initWithObjectsAndKeys:FLICKR_API_KEY,#"api_key",self.ID,#"photo_id",nil];
[flickrAPIRequest callAPIMethodWithGET:flickrAPIMethodToCall arguments:requestArguments];
[pool release];
I have implemented the callback method that would catch the response from the API and update the FlickrImage instance with the geolocation data - but it never gets called. Here's where the instances get created:
NSDictionary *photosDictionary = [inResponseDictionary valueForKeyPath:#"photos.photo"];
NSDictionary *photoDictionary;
FlickrImage *flickrImage;
for (photoDictionary in photosDictionary) {
flickrImage = [[FlickrImage alloc] init];
flickrImage.thumbnailURL = [[appDelegate sharedDelegate].flickrAPIContext photoSourceURLFromDictionary:photoDictionary size:OFFlickrThumbnailSize];
flickrImage.hasLocation = TRUE; // TODO this is actually to be determined...
flickrImage.ID = [NSString stringWithFormat:#"%#",[photoDictionary valueForKeyPath:#"id"]];
flickrImage.owner = [photoDictionary valueForKeyPath:#"owner"];
flickrImage.title = [photoDictionary valueForKeyPath:#"title"];
[self.flickrImages addObject:[flickrImage retain]];
[flickrImage release];
[photoDictionary release];
}
The retain is there because I thought it might help solve this but it doesn't - and doesn't the NSMutableArray (flickrImages is a NSMutableArray) retain its members anyway?
EDIT I should add that the getLocation method (first code snippet) is launched in a thread:
[NSThread detachNewThreadSelector:#selector(getLocation) toTarget:self withObject:nil];
Your delegate method is never being called because the request is never being made. When you call callAPIMethodWithGET:, it sets up communications to run asynchronously on the current thread's run loop, then returns immediately. That way you can safely call it on the main thread without blocking.
Because you are calling the method from a thread you created yourself, it does not see the main run loop, but the run loop for your new thread. However, because you never execute the run loop, the messages are never sent, a response is never received, and your delegate is never called.
You could fix this by calling [[NSRunLoop currentRunLoop] run] in your new thread. That will let the work happen. But in this case would be easier to never detach a new thread in the first place. Your program won't block, and you won't have to worry about your delegate method needing to be reentrant.
I've also run into this problem when requesting and parsing XML on a different thread my solution was to do this:
while([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:start] && !isFinished){
}
Where start = [NSDate dateWithTimeIntervalSinceNow:3]; this is basically a timeout so that it doesn't live forever and isFinished is set to true when my parsing has completed.
I'm not familiar with these flicker API wrappers, but in this code:
NSDictionary *requestArguments = [[NSDictionary alloc] initWithObjectsAndKeys:FLICKR_API_KEY,#"api_key",self.ID,#"photo_id",nil];
Are you certain that both FLICKR_API_KEY, and self.ID are not nil? If either of them is nil, you'll end up with a dictionary that has less items in it than you intend.
Could you post the callback method(s) you have implemented – this could be just down to a simple typo, as it appears OFFlickrAPIRequest won’t do anything if the delegate does not implement the required callback.
Did you also implement flickrAPIRequest:didFailWithError: to see if there was an error returned from the API call?
Okay, I did solve it, with help from some of the suggestions above.
I did remove the extra retain because it did in fact create a memory leak. It did not look right from the outset, so my gut feeling about that is worth something, which is a good thing ;)
I removed the redundant threading because the API call is already asynchronous and does not require an additional thread to be non-blocking. After that, the callback method was being called but I ran into different problems concerning object retention. If interested you might want to check out that question, too
Thanks all.
The setDelegate method of OFFlickrAPIRequest does not retain the delegate like it should. This means you're stuck ensuring that your delegate is alive as long as the request is (or patching the class to properly own its own references).