How do you trigger a block after a delay, like -performSelector:withObject:afterDelay:?

373,854

Solution 1

I think you're looking for dispatch_after(). It requires your block to accept no parameters, but you can just let the block capture those variables from your local scope instead.

int parameter1 = 12;
float parameter2 = 144.1;

// Delay execution of my block for 10 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    NSLog(@"parameter1: %d parameter2: %f", parameter1, parameter2);
});

More: https://developer.apple.com/documentation/dispatch/1452876-dispatch_after

Solution 2

You can use dispatch_after to call a block later. In Xcode, start typing dispatch_after and hit Enter to autocomplete to the following:

enter image description here

Here's an example with two floats as "arguments." You don't have to rely on any type of macro, and the intent of the code is quite clear:

Swift 3, Swift 4

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Sum of times: \(time1 + time2)")
}

Swift 2

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
        println("Sum of times: \(time1 + time2)")
}

Objective C

CGFloat time1 = 3.49;
CGFloat time2 = 8.13;

// Delay 2 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    CGFloat newTime = time1 + time2;
    NSLog(@"New time: %f", newTime);
});

Solution 3

How about using Xcode built-in code snippet library?

enter image description here

Update for Swift:

Many up votes inspired me to update this answer.

The build-in Xcode code snippet library has dispatch_after for only objective-c language. People can also create their own Custom Code Snippet for Swift.

Write this in Xcode.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(<#delayInSeconds#> * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {
        <#code to be executed after a specified delay#>
    })

Drag this code and drop it in the code snippet library area. enter image description here

Bottom of the code snippet list, there will be a new entity named My Code Snippet. Edit this for a title. For suggestion as you type in the Xcode fill in the Completion Shortcut.

For more info see CreatingaCustomCodeSnippet.

Update Swift 3

Drag this code and drop it in the code snippet library area.

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(<#delayInSeconds#>)) {
    <#code to be executed after a specified delay#>
}

Solution 4

Expanding on Jaime Cham's answer I created a NSObject+Blocks category as below. I felt these methods better matched the existing performSelector: NSObject methods

NSObject+Blocks.h

#import <Foundation/Foundation.h>

@interface NSObject (Blocks)

- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay;

@end

NSObject+Blocks.m

#import "NSObject+Blocks.h"

@implementation NSObject (Blocks)

- (void)performBlock:(void (^)())block
{
    block();
}

- (void)performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay
{
    void (^block_)() = [block copy]; // autorelease this if you're not using ARC
    [self performSelector:@selector(performBlock:) withObject:block_ afterDelay:delay];
}

@end

and use like so:

[anyObject performBlock:^{
    [anotherObject doYourThings:stuff];
} afterDelay:0.15];

Solution 5

For Swift I've created a global function, nothing special, using the dispatch_after method. I like this more as it's readable and easy to use:

func performBlock(block:() -> Void, afterDelay delay:NSTimeInterval){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), block)
}

Which you can use as followed:

performBlock({ () -> Void in
    // Perform actions
}, afterDelay: 0.3)
Share:
373,854
Egil
Author by

Egil

Updated on July 08, 2022

Comments

  • Egil
    Egil almost 2 years

    Is there a way to call a block with a primitive parameter after a delay, like using performSelector:withObject:afterDelay: but with an argument like int/double/float?

  • adib
    adib over 13 years
    The problem is if any of the block's variables refer to autoreleased objects, those objects may be gone by the time the block is started. In contrast performSelector:withObject:afterDelay: retains all its parameter until after the selector is performed.
  • Ryan
    Ryan over 13 years
    Actually, that's not true. Objects captured by a block that are not marked as being in __block storage are retained by the block, and get released by the block when it is destroyed (when its retain count goes to 0). Here's the documentation on that: developer.apple.com/library/mac/documentation/Cocoa/Conceptu‌​al/…
  • samvermette
    samvermette almost 13 years
    this dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC) snippet is nasty. Isn't there a cleaner way for this?
  • Tudor
    Tudor over 12 years
    @Ryan, the macro raises a missing ) error, but nothing is missing. Any other way to send a variable instead of 10ull ? EDIT: nevermind. I'm using int64_t delta = (int64_t)(1.0e9 * delay); inspired by an answer bellow.
  • Ryan
    Ryan over 12 years
    Yeah, the ill probably isn't necessary, since C will automatically upcast the int to match the size of NSEC_PER_SEC. I'll edit my answer to remove it for clarity.
  • BadPirate
    BadPirate over 12 years
    If this is called from the main thread, does it get dispatched from the main thread?
  • Ryan
    Ryan over 12 years
    Yes, dispatch_get_current_queue() always returns the queue from which the code is being run. So when this code is run from the main thread, the block will also be executed on the main thread.
  • Besi
    Besi over 12 years
    @Jaimie Cham Why do you think going through GCD is difficult?
  • Dan Rosenstark
    Dan Rosenstark about 12 years
    Very cool, it seems that (under ARC) the dispatch_after retains the block until it executes it. This eliminates so much complicated code...
  • adib
    adib about 12 years
    I wonder why (under ARC) doing the equivalent of this using performSelector:withObject:afterDelay: on [[NSOperationQueue] mainQueue] often fails with a crash? Snippet here: gist.github.com/2020649
  • fishinear
    fishinear almost 12 years
    Going through GCD does have slightly different behavior than PerformSelector:afterDelay:, so there may be reasons to not use GCD. See, for example, the following question: stackoverflow.com/questions/10440412/…
  • Timo
    Timo almost 12 years
    Might I suggest the InnerBand library! Among many other features for common tasks, it adds macros to do just this, with a choice of which thread on top. Highly recommended.
  • c roald
    c roald over 11 years
    Why do you copy the block before passing it to performSelector?
  • user102008
    user102008 over 11 years
    However, this requires you to be running in a dispatch queue, which would not be the case if you are in a custom thread.
  • Jason Cabot
    Jason Cabot about 11 years
    Just expanding on @Ryan's comment: If you are using ARC, even objects marked as __block are retained by the block, it's only objects marked as __weak or __unsafe_unretained that aren't. See the transitioning to ARC guide by Apple: developer.apple.com/library/ios/#releasenotes/ObjectiveC/… for a more detailed explanation
  • meaning-matters
    meaning-matters about 11 years
    The delay should be of NSTimeInterval (which is a double). #import <UIKit/UIKit.h> is not needed. And, I don't see why - (void)performBlock:(void (^)())block; could be useful, so can be removed from header.
  • Oliver Pearmain
    Oliver Pearmain about 11 years
    @meaning-matters, both valid points +1, I've updated my answer accordingly.
  • Matej
    Matej about 11 years
    dispatch_get_current_queue() is deprecated now
  • progrmr
    progrmr over 10 years
    you can use dispatch_get_main_queue() instead of the current queue if you want it to run on the main queue.
  • Martin Berger
    Martin Berger over 10 years
    You can use dispatch_get_global_queue if you dont want to do processing on main que.
  • Victor C.
    Victor C. over 10 years
    With global que, dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, nil), ^{ NSLog(@"parameter1: %d parameter2: %f", parameter1, parameter2); });
  • cprcrack
    cprcrack over 10 years
    Besides NSEC_PER_SEC, NSEC_PER_MSEC does also exist, in case you want to specify milliseconds ;)
  • Jaime Cham
    Jaime Cham over 10 years
    Sorry for the delay. @croald: I think you need the copy to move the block from the stack to the heap.
  • Jaime Cham
    Jaime Cham over 10 years
    @Besi: more wordy and hides the intent.
  • malhal
    malhal over 10 years
    I have put in an edit request to get this changed from the deprecated dispatch_get_current_queue to dispatch_get_main_queue.
  • malhal
    malhal over 10 years
    Be careful the delay time is not a double. So just don't try NSEC_PER_SEC * 0.5 for half a second it won't work! You need to drop to milliseconds and use NSEC_PER_MSEC * 500. So you should change your code sample to: int delayInSeconds = 2 to show people can't use fractions of NSEC_PER_SEC.
  • Jordan
    Jordan almost 10 years
    2 years later: "Sorry for the delay, just grabbing a bite to eat"
  • anders
    anders over 9 years
    this is cleaner than GCD thats why
  • Peter Lapisu
    Peter Lapisu over 9 years
    this is not correct at all, the performSelector has to be removed explicitly on dealloc, or else you will run into really weird behaviour and crashes, more correct is to use the dispatch_after
  • Supertecnoboff
    Supertecnoboff about 9 years
    Does anyone actually use this feature in Xcode? I prefer to just type it as the code suggestions popup and are just as easy to use.
  • levous
    levous almost 9 years
    class DispatchHelper { class func callbackWithDelay(waitSeconds: Double, callback:(() -> ())){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(waitSeconds * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in callback() } } }
  • levous
    levous almost 9 years
    sorry, code doesn't format nicely in comments. Copy my last comment and use DispatchHelper.callbackWithDelay(2.0, callback: { /* your code here */ })
  • Lars Blumberg
    Lars Blumberg over 8 years
    I suggest to swap arguments and renaming it to after. Then you can write: after(2.0){ print("do somthing") }
  • arshu
    arshu about 8 years
    Until know i just thought copy & paste was the easiest way to code. Now i just drag & drop.... hahaha
  • Bastian
    Bastian almost 8 years
    there is a typo: mainQueue, instead of mainQueue)
  • nalexn
    nalexn over 7 years
    This is implicit that your delay function executes code from background thread. Someone using your example can have really tough times debugging the app being crashed, if they put any UI - related code inside // delayed code section.
  • Jeehut
    Jeehut over 7 years
    By default my method uses the main thread so that shouldn't happen. See the dispatchLevel defaulting to .Main?
  • junjie
    junjie almost 7 years
    @malhal Actually, NSEC_PER_SEC * 0.5 would work the same as NSEC_PER_MSEC * 500. While you're correct to note that dispatch_time expects a 64-bit integer, the value it expects is in nanoseconds. NSEC_PER_SEC is defined as 1000000000ull, and multiplying that with a floating-point constant 0.5 would implicitly perform a floating-point arithmetic, yielding 500000000.0, before it is explicitly casted back to a 64-bit integer. So it's perfectly acceptable to use a fraction of NSEC_PER_SEC.
  • QuangDT
    QuangDT about 5 years
    WARNING! dispatch_after fires approx 10% later than the interval!
  • user3741598
    user3741598 about 4 years
    I'm trying to use this in a UIView / drawRect to create bar graphs - pulling the data down (NSURL, parse) in a method is somewhat delayed - but putting the 'CGContextRef context = UIGraphicsGetCurrentContext();' (and other CGContext calls to set line width, define start/end points, and then stroke the line) are failing because the context=nil - apparently that must be called outside of the dispatch_after - and doing that does get a non-nil context - but it is still 'invalid' when the other calls are made that need it as input. Any ideas? (I opened 60555159 which contains the code).
  • Motti Shneor
    Motti Shneor about 3 years
    OP tagged this question with ObjC, and also asks for alternative to an ObjC selector PerformSelector:AfterDelay: etc.
  • Rakshitha Muranga Rodrigo
    Rakshitha Muranga Rodrigo about 3 years
    This saved the day. Thanks.