views:

372

answers:

4

Hi guys,

I'd like to have a method be executed after the current method has passed and the UI has been updated. For that purpose, I'm using [object performSelector:@selector(someSelector) withObject:someObject afterDelay:0.0] right now. According to Apple's documentation, this creates a NSTimer which will then trigger and append the selector to the current NSRunLoop. But I don't consider this very elegant. Is there an easy way to directly enqueue the selector to the current run loop, without having Cocoa create a Timer etc.?

Would performSelectorOnMainThread:withObject:waitUntilDone: (if I'm on the main thread) or performSelector:onThread:withObject:waitUntilDone: with waitUntilDone:NO do what I want with less overhead?

Cheers and thanks in advance

MrMage

+3  A: 

Cocoa is event-driven. You don't "enqueue a selector within the current run loop". To put it simplistically: An event sent to the application (user input, a timer, network activity ...) causes the run loop to run, which causes things to happen in that run of the loop. There are of course "details", but this is the most basic behavior.

If you want to put off performing some selector to the end of the current run loop, call it last, or ask it to be run on a (very near) upcoming run of the loop. The -performSelector:... methods are the correct way to do this. They create a timer which results in an event which causes things to happen.

For more information, see the Cocoa Event-Handling Guide.

Joshua Nozzi
The documentation I cited reads 'Performs the specified selector on the current thread during the next run loop cycle and after an optional delay period.' Doesn't that mean that the selector somehow has to be enqueued to the next run loop cycle?
MrMage
Sure does. :-) Thats via a timer fired event.
Joshua Nozzi
Accepted this one because it gave me the most insight into what is really happening.
MrMage
+2  A: 

I don't see anything inelegant about the -performSelector:withObject:afterDelay: method that you highlight. This method simply enqueues a task to be performed after the completion of the current cycle of the run loop. From the documentation in the section you linked to:

Performs the specified selector on the current thread during the next run loop cycle and after an optional delay period. Because it waits until the next run loop cycle to perform the selector, these methods provide an automatic mini delay from the currently executing code. Multiple queued selectors are performed one after another in the order they were queued.

An NSTimer object is not created to manage this, the selector is simply enqueued to be run after a certain delay (a small delay means immediately after the completion of the run loop cycle). For actions that you wish to happen after updates to the UI take place, this is the simplest technique.

For more explicit, threaded queueing, you could look at NSOperations and NSOperationQueues. An NSOperationQueue with a maxConcurrentOperationCount of 1 can run operations in order, one after the other.

Brad Larson
It does create a timer: http://developer.apple.com/mac/library/documentation/cocoa/reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/performSelector:withObject:afterDelay:`This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector.`
MrMage
You're both sort of right. -performSelector:withObject:afterDelay: uses the core foundation timer API, not an NSTimer instance.
NSResponder
+1  A: 

I have used this technique many times myself, and I don't think it's that inelegant... however, an alternative you could try is:

performSelectorOnMainThread:withObject:waitUntilDone:NO.

Just because you are already on the main thread, does not mean it would not work (in fact the documents reference behaviour that will happen when called from the main thread)... and I think it would have the same behavior when waitUntilDone is set to NO, where it queues up the request to execute the selector and have it run when the current run-loop ends.

Kendall Helmstetter Gelner
I wrote about that in my own post...
MrMage
Hmm, my apologies - I guess I missed that at the end. It is a better way to do what you want to do though.
Kendall Helmstetter Gelner
+2  A: 

I prefer the NSRunLoop method "performSelector:target:argument:order:modes:". It's guaranteed to not execute the selector until the next iteration of the run loop, and you don't have to mess around with specifying arbitrary delays, etc.

Richard Stahl