Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enqueue a selector to the run loop - is [NSObject performSelector:withObject:afterDelay:] the way to go?

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

like image 549
MrMage Avatar asked Nov 14 '09 16:11

MrMage


4 Answers

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.

like image 158
Joshua Nozzi Avatar answered Sep 22 '22 07:09

Joshua Nozzi


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.

like image 26
Brad Larson Avatar answered Sep 21 '22 07:09

Brad Larson


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.

like image 38
Richard Stahl Avatar answered Sep 22 '22 07:09

Richard Stahl


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.

like image 23
Kendall Helmstetter Gelner Avatar answered Sep 24 '22 07:09

Kendall Helmstetter Gelner