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
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.
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.
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.
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.