88

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.

Rits
  • 5,105
  • 4
  • 42
  • 53
  • 2
    This came a month later: http://stackoverflow.com/questions/4139219/how-do-you-trigger-a-block-after-a-delay-like-performselectorwithobjectafter – Jacksonkr May 17 '12 at 22:24

6 Answers6

107

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.

PeyloW
  • 36,742
  • 12
  • 80
  • 99
John Calsbeek
  • 35,947
  • 7
  • 94
  • 101
  • 5
    note: 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. – justin Oct 24 '10 at 08:40
  • 8
    I 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 does `self` have in the firing of the block? You'd be better-off writing a C function `RunBlockAfterDelay(void (^block)(void), NSTimeInterval delay)`, though this does necessitate creating a temporary object whose sole job is to implement `-fireBlockAfterDelay:`. – Lily Ballard Oct 24 '10 at 08:55
  • 19
    @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. – PeyloW Oct 24 '10 at 17:30
  • PeyloW is right, the cleanest way to do this would probably be to use Grand Central Dispatch. – John Calsbeek Oct 24 '10 at 18:40
  • 3
    No, 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 Jan 20 '11 at 17:00
  • 2
    @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. – Jonathan Sterling Mar 26 '11 at 18:30
  • 4
    Apple 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? – Catfish_Man Mar 26 '11 at 19:02
  • 1
    @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. – Tim Jun 10 '11 at 02:06
  • 6
    I fully agree with Kevin's remark: Adding a category on `NSObject` that basically replaces a call to `dispatch_after` with a method that calls a method which (at least in the iOS Simulator on Lion) _seems_ to be implemented by calling `dispatch_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 plain `dispatch_after` out of the box… – danyowdee Nov 27 '11 at 01:20
  • If 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? – knagode Apr 17 '12 at 14:27
  • Awesome! Better than using an empty animation block! – Nicolas Miari Feb 13 '13 at 09:37
  • Worked 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 Mar 12 '13 at 14:55
  • @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. – RonLugge Jun 05 '13 at 03:44
  • @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. – John Calsbeek Jun 05 '13 at 03:58
  • Allright, 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 – Peter Lapisu Feb 06 '15 at 13:33
41

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.

Nick Moore
  • 15,547
  • 6
  • 61
  • 83
  • 1
    This 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. – Catfish_Man Jan 20 '11 at 17:01
  • 3
    The 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. – danyowdee Nov 27 '11 at 01:30
  • 3
    @danyowdee As mentioned in other response, that is wrong. "A Block Should Always Be the Last Argument to a Method" as per http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html – SilverSideDown Mar 12 '13 at 14:57
23

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
}];
ninjaneer
  • 6,951
  • 8
  • 60
  • 104
16

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

Greg Combs
  • 4,252
  • 3
  • 33
  • 47
  • 4
    +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. – JLundell Oct 01 '11 at 20:00
11

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...  
    });
Despotovic
  • 1,807
  • 2
  • 20
  • 24
  • this is the correct solution, it might look complicated but Xcode auto-completes the pattern when typing in dispatch_after. – malhal Jul 26 '15 at 19:20
1

There's a nice, complete category that handles this situation here:

https://gist.github.com/955123

Duane Fields
  • 1,331
  • 12
  • 20