Blocks instead of performSelector:withObject:afterDelay:
Solution 1
There's no built-in way to do that, but it's not too bad to add via a category:
@implementation NSObject (PerformBlockAfterDelay)
- (void)performBlock:(void (^)(void))block
afterDelay:(NSTimeInterval)delay
{
block = [[block copy] autorelease];
[self performSelector:@selector(fireBlockAfterDelay:)
withObject:block
afterDelay:delay];
}
- (void)fireBlockAfterDelay:(void (^)(void))block {
block();
}
@end
Credit to Mike Ash for the basic implementation.
Solution 2
Here's a simple technique, based on GCD, that I'm using:
void RunBlockAfterDelay(NSTimeInterval delay, void (^block)(void))
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*delay),
dispatch_get_current_queue(), block);
}
I'm not a GCD expert, and I'd be interested in comments on this solution.
Solution 3
Another way (perhaps the worst way to do this for many reasons) is:
[UIView animateWithDuration:0.0 delay:5.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
} completion:^(BOOL finished) {
//do stuff here
}];
Solution 4
If you specifically need a longer delay, the solutions above work just fine. I've used @nick's approach with great success.
However, if you just want your block to run during the next iteration of the main loop, you can trim it down even further with just the following:
[[NSOperationQueue mainQueue] addOperationWithBlock:aBlock];
This is akin to using performSelector: with afterDelay of 0.0f
Solution 5
I used similar code like this:
double delayInSeconds = 0.2f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//whatever you wanted to do here...
});
Rits
22 year old Dutch iPhone app developer with interest in Ruby on Rails.
Updated on July 08, 2022Comments
-
Rits almost 2 years
I often want to execute some code a few microseconds in the future. Right now, I solve it like this:
- (void)someMethod { // some code }
And this:
[self performSelector:@selector(someMethod) withObject:nil afterDelay:0.1];
It works, but I have to create a new method every time. Is it possible to use blocks instead of this? Basically I'm looking for a method like:
[self performBlock:^{ // some code } afterDelay:0.1];
That would be really useful to me.
-
justin over 13 yearsnote: you should always choose a prefix for your category methods, such as
mon_performBlock:afterDelay:
. this will reduce the possibility of your methods colliding with other implementations. the most common example: apple decides to add this method -- oops, your method will not replace what already loaded and if it did... it would be even more painful. -
Lily Ballard over 13 yearsI should note that the idea of
[self performBlock:^{/* some block */} afterDelay:0.1]
doesn't make much sense. Why is this attached to an object at all? What role doesself
have in the firing of the block? You'd be better-off writing a C functionRunBlockAfterDelay(void (^block)(void), NSTimeInterval delay)
, though this does necessitate creating a temporary object whose sole job is to implement-fireBlockAfterDelay:
. -
PeyloW over 13 years@Kevin: For convenience only. If you wanted to to it properly than using GDC directly and call the
dispatch_after(dispatch_time_t, dispatch_queue_t, dispatch_block_t)
function is the correct way to do it. -
John Calsbeek over 13 yearsPeyloW is right, the cleanest way to do this would probably be to use Grand Central Dispatch.
-
Catfish_Man over 13 yearsNo, the worst case scenario is that the Cocoa team has to choose a less than optimal name because someone took the good name. Or if your app isn't important enough to rate that treatment, let it crash on a new OS as the behavior changes.
-
Catfish_Man over 13 yearsThis will do what you want, but I'd encourage you to continue specifying the target queue explicitly; it's not that much more code, and it makes it very clear what execution context things will be running in. A macro for dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*delay) might be a more effective way to reduce boilerplate.
-
Jonathan Sterling about 13 years@Catfish_Man Didn't see your reply until now. That's entirely ridiculous; they're not going to base their variable-naming decisions on your app, no matter how important it is. If something changes in the API, your app may crash: big deal! Update it. You always have to update your app for new OS versions anyway. There's no problem.
-
Catfish_Man about 13 yearsApple engineering teams aren't allowed to break critical apps in updates. You really think OSX could ship with every copy of Photoshop crashing on launch, say?
-
Tim almost 13 years@Catfish_Man This is why developer previews exist. You think the team at Adobe would sit on their butts, wait for the new OS version to come out, THEN update their app when it crashes? Registered developers get a few months, at minimum, with the beta OS before it comes out. Even the most inefficient dev team in the world could rename a method in that time.
-
JLundell over 12 years+1, but an advantage to the existing performSelector is the ability to cancel. That's less of an issue with zero delay, but even then it can be useful to resolve potential races.
-
danyowdee over 12 yearsI fully agree with Kevin's remark: Adding a category on
NSObject
that basically replaces a call todispatch_after
with a method that calls a method which (at least in the iOS Simulator on Lion) seems to be implemented by callingdispatch_after
in order to do nothing but call a method that invokes a parameterless block, is a tad too much indirection and wrapping, in my opinion. Especially since Xcode 4 even ships with a code snippet for plaindispatch_after
out of the box… -
danyowdee over 12 yearsThe only thing I'd add is that having the parameters the other way around would make the invocations a little more intuitive, as their order would then follow the order of the nouns in the function name.
-
knagode about 12 yearsIf I put above code into separate .h and .m files, program crashes with error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GameLayer performBlock:afterDelay:]: unrecognized selector sent to instance. Someone knows how to do it properly?
-
Nicolas Miari about 11 yearsAwesome! Better than using an empty animation block!
-
SilverSideDown about 11 yearsWorked great, thank you. @danyowdee Sure the Xcode snippet there, but it is very verbose. 3 pretty full lines, somewhere inline in your code. I like this category approach much better, shorter & cleaner. The only criticism is that it doesn't have the block as the last parameter as is the convention. But easy change to make to performAfterDelay:block:
-
SilverSideDown about 11 years@danyowdee As mentioned in other response, that is wrong. "A Block Should Always Be the Last Argument to a Method" as per developer.apple.com/library/ios/#documentation/cocoa/conceptual/…
-
RonLugge almost 11 years@KevinBallard I'd prefer this category (which should be included into the main iOS SDK, if you ask me!) because it's clearly object oriented. The dispatch queue functions are much less clear, not easy to use, and aren't very object oriented, which makes them confusing and jarring to use in an OOP environment.
-
John Calsbeek almost 11 years@RonLugge I disagree. This method is not object-oriented. It's not usefully polymorphic, and it's only even a method because there's a large class of other methods with similar signatures (which are only methods because they send a message to the object that they're called on). This is merely a pinch of sugar to make it all look uniform, if you value that.
-
Peter Lapisu about 9 yearsAllright, but i see a major problem, what happens, when the object is dealloced, before the selector is triggered... you should remove all previous perform requests on dealloc
-
malhal almost 9 yearsthis is the correct solution, it might look complicated but Xcode auto-completes the pattern when typing in dispatch_after.