Blocks instead of performSelector:withObject:afterDelay:

39,264

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...  
    });
Share:
39,264
Rits
Author by

Rits

22 year old Dutch iPhone app developer with interest in Ruby on Rails.

Updated on July 08, 2022

Comments

  • Rits
    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
    justin over 13 years
    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.
  • Lily Ballard
    Lily Ballard over 13 years
    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:.
  • PeyloW
    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
    John Calsbeek over 13 years
    PeyloW is right, the cleanest way to do this would probably be to use Grand Central Dispatch.
  • Catfish_Man
    Catfish_Man over 13 years
    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
    Catfish_Man over 13 years
    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.
  • Jonathan Sterling
    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
    Catfish_Man about 13 years
    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?
  • Tim
    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
    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
    danyowdee over 12 years
    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
    danyowdee over 12 years
    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.
  • knagode
    knagode about 12 years
    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?
  • Nicolas Miari
    Nicolas Miari about 11 years
    Awesome! Better than using an empty animation block!
  • SilverSideDown
    SilverSideDown about 11 years
    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
    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/concept‌​ual/…
  • RonLugge
    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
    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
    Peter Lapisu about 9 years
    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
  • malhal
    malhal almost 9 years
    this is the correct solution, it might look complicated but Xcode auto-completes the pattern when typing in dispatch_after.