773

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?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Egil
  • 8,101
  • 4
  • 17
  • 6

20 Answers20

1226

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

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ryan
  • 16,626
  • 2
  • 23
  • 20
  • 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. – adib Nov 20 '10 at 07:43
  • 88
    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: http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW3 – Ryan Nov 23 '10 at 22:32
  • 10
    this `dispatch_time(DISPATCH_TIME_NOW, 10ull * NSEC_PER_SEC)` snippet is nasty. Isn't there a cleaner way for this? – samvermette Jun 25 '11 at 21:56
  • @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. – Tudor Aug 18 '11 at 14:32
  • 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. – Ryan Aug 20 '11 at 22:23
  • If this is called from the main thread, does it get dispatched from the main thread? – BadPirate Sep 24 '11 at 00:32
  • 8
    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. – Ryan Sep 26 '11 at 17:48
  • 4
    Very cool, it seems that (under ARC) the `dispatch_after` retains the block until it executes it. This eliminates so much complicated code... – Dan Rosenstark Mar 08 '12 at 00:34
  • 1
    I wonder why (under ARC) doing the equivalent of this using `performSelector:withObject:afterDelay:` on `[[NSOperationQueue] mainQueue]` often fails with a crash? Snippet here: https://gist.github.com/2020649 – adib Mar 12 '12 at 08:08
  • 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. – Timo Jul 05 '12 at 15:05
  • However, this requires you to be running in a dispatch queue, which would not be the case if you are in a custom thread. – user102008 Dec 19 '12 at 21:22
  • 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: http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226-CH1-SW9 for a more detailed explanation – Jason Cabot Feb 25 '13 at 15:45
  • 20
    `dispatch_get_current_queue()` is deprecated now – Matej Apr 08 '13 at 12:45
  • 2
    you can use `dispatch_get_main_queue()` instead of the current queue if you want it to run on the main queue. – progrmr Aug 27 '13 at 22:38
  • You can use `dispatch_get_global_queue` if you dont want to do processing on main que. – Martin Berger Oct 16 '13 at 11:56
  • 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); }); – Victor C. Oct 24 '13 at 13:10
  • 10
    Besides NSEC_PER_SEC, NSEC_PER_MSEC does also exist, in case you want to specify milliseconds ;) – cprcrack Oct 24 '13 at 16:09
  • I have put in an edit request to get this changed from the deprecated dispatch_get_current_queue to dispatch_get_main_queue. – malhal Nov 12 '13 at 02:03
  • 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 Jul 02 '15 at 15:35
  • sorry, code doesn't format nicely in comments. Copy my last comment and use DispatchHelper.callbackWithDelay(2.0, callback: { /* your code here */ }) – levous Jul 02 '15 at 15:37
  • WARNING! dispatch_after fires approx 10% later than the interval! – RunLoop May 08 '19 at 17:04
  • 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). – user3741598 Mar 05 '20 at 23:19
533

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);
});
farzadshbfn
  • 2,710
  • 1
  • 18
  • 38
Steven Hepting
  • 12,394
  • 8
  • 40
  • 50
  • 49
    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. – malhal Nov 12 '13 at 02:08
  • 16
    @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`. – junjie Jul 02 '17 at 14:05
  • this answer is trash because I can't copy paste it. – CDM social medias in bio Dec 20 '22 at 18:00
205

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#>
}
Community
  • 1
  • 1
Warif Akhand Rishi
  • 23,920
  • 8
  • 80
  • 107
  • 19
    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. – Supertecnoboff Apr 18 '15 at 11:09
  • 6
    Until know i just thought copy & paste was the easiest way to code. Now i just drag & drop.... hahaha – arshu Mar 14 '16 at 10:04
59

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];
Oliver Pearmain
  • 19,885
  • 13
  • 86
  • 90
  • 5
    The `delay` should be of `NSTimeInterval` (which is a `double`). `#import ` is not needed. And, I don't see why `- (void)performBlock:(void (^)())block;` could be useful, so can be removed from header. – meaning-matters Mar 06 '13 at 23:47
  • @meaning-matters, both valid points +1, I've updated my answer accordingly. – Oliver Pearmain Mar 07 '13 at 09:03
  • 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 – Peter Lapisu Feb 06 '15 at 13:24
21

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)
Antoine
  • 23,526
  • 11
  • 88
  • 94
21

Perhaps simpler than going thru GCD, in a class somewhere (e.g. "Util"), or a Category on Object:

+ (void)runBlock:(void (^)())block
{
    block();
}
+ (void)runAfterDelay:(CGFloat)delay block:(void (^)())block 
{
    void (^block_)() = [[block copy] autorelease];
    [self performSelector:@selector(runBlock:) withObject:block_ afterDelay:delay];
}

So to use:

[Util runAfterDelay:2 block:^{
    NSLog(@"two seconds later!");
}];
Jaime Cham
  • 1,494
  • 1
  • 15
  • 16
16

Here are my 2 cents = 5 methods ;)

I like encapsulate these details and have AppCode tell me how to finish my sentences.

void dispatch_after_delay(float delayInSeconds, dispatch_queue_t queue, dispatch_block_t block) {
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, queue, block);
}

void dispatch_after_delay_on_main_queue(float delayInSeconds, dispatch_block_t block) {
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_after_delay(delayInSeconds, queue, block);
}

void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

void dispatch_async_on_background_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), block);
}

void dispatch_async_on_main_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_main_queue(), block);
}
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
8

PerformSelector:WithObject always takes an object, so in order to pass arguments like int/double/float etc..... You can use something like this.

//NSNumber is an object..

[self performSelector:@selector(setUserAlphaNumber:)
     withObject: [NSNumber numberWithFloat: 1.0f]       
     afterDelay:1.5];



-(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];

}

Same way you can use [NSNumber numberWithInt:] etc.... and in the receiving method you can convert the number into your format as [number int] or [number double].

Augustine
  • 1,714
  • 1
  • 17
  • 21
8

The dispatch_after function dispatches a block object to a dispatch queue after a given period of time. Use below code to perform some UI related taks after 2.0 seconds.

            let delay = 2.0
            let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
            let mainQueue = dispatch_get_main_queue()

            dispatch_after(delayInNanoSeconds, mainQueue, {

                print("Some UI related task after delay")
            })

In swift 3.0 :

            let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(2.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
            DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: {

          })
Himanshu Mahajan
  • 4,779
  • 2
  • 36
  • 29
8

Xcode 10.2 and Swift 5 and above

DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
   // code to execute                 
})

ObjC version

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    //code to execute
});
midhun p
  • 1,987
  • 18
  • 24
  • 1
    OP tagged this question with ObjC, and also asks for alternative to an ObjC selector PerformSelector:AfterDelay: etc. – Motti Shneor Mar 11 '21 at 13:47
6

Press Cmd + Shift + L to show the Xcode built-in code snippet library:

enter image description here

Look for dispatch after, then, just drag and drop to your code.

oskarko
  • 3,382
  • 1
  • 26
  • 26
5

Here's a handy helper to prevent making the annoying GCD call over and over again:

public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: @escaping () -> Void) {
    let dispatchTime = DispatchTime.now() + seconds
    dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}

public enum DispatchLevel {
    case main, userInteractive, userInitiated, utility, background
    var dispatchQueue: DispatchQueue {
        switch self {
        case .main:                 return DispatchQueue.main
        case .userInteractive:      return DispatchQueue.global(qos: .userInteractive)
        case .userInitiated:        return DispatchQueue.global(qos: .userInitiated)
        case .utility:              return DispatchQueue.global(qos: .utility)
        case .background:           return DispatchQueue.global(qos: .background)
        }
    }
}

Now you simply delay your code on the Main thread like this:

delay(bySeconds: 1.5) { 
    // delayed code
}

If you want to delay your code to different thread:

delay(bySeconds: 1.5, dispatchLevel: .background) { 
    // delayed code that will run on background thread
}

If you prefer a Framework that also has some more handy features then checkout HandySwift. You can add it to your project via Carthage then use it exactly like in the examples above:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}
Jeehut
  • 20,202
  • 8
  • 59
  • 80
  • 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. – nalexn Sep 05 '16 at 04:26
  • By default my method uses the main thread so that shouldn't happen. See the dispatchLevel defaulting to .Main? – Jeehut Sep 05 '16 at 05:32
5

Here's the Swift 3 way to queue work after a delay.

DispatchQueue.main.asyncAfter(
  DispatchTime.now() + DispatchTimeInterval.seconds(2)) {
    // do work
}
cpimhoff
  • 675
  • 6
  • 11
4

There's a nice one in the BlocksKit framework.

BlocksKit

(and the class)

BBlocksKit.m

NANNAV
  • 4,875
  • 4
  • 32
  • 50
psy
  • 2,791
  • 26
  • 26
4

In swift 3, We can simply use DispatchQueue.main.asyncAfter function to trigger any function or action after the delay of 'n' seconds. Here in code we have set delay after 1 second. You call any function inside the body of this function which will trigger after the delay of 1 second.

let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {

    // Trigger the function/action after the delay of 1Sec

}
Pang
  • 9,564
  • 146
  • 81
  • 122
Rouny
  • 284
  • 5
  • 18
3

Updated in April 2021 with Xcode 12.4. This still works, except now the + button presents more icons, including Views library and Modifiers library (see below), and Views may be the default. With View and Modifiers library icons So be sure to select the Snippets library, as in the image below.


(verified on Xcode 11.3.1 in June 2020)

Xcode provides a code snippet to do this. You just have to enter the delay value and the code you wish to run after the delay.

  1. click on the + button at the top right of Xcode, while you are editing some code (rather than, say, in the Project Navigator, where other libraries like Capabilities would be presented)
  2. Make sure the Snippets library (see screenshot, the icon with { }) is selected, out of the icons available.
  3. search for after
  4. It will return only 1 search result, which is the desired snippet (see screenshot). Double click it and you're good to go.

screenshot illustrating how to get the snippet from within Xcode itself

auspicious99
  • 3,902
  • 1
  • 44
  • 58
1

Here is how you can trigger a block after a delay in Swift:

runThisAfterDelay(seconds: 2) { () -> () in
    print("Prints this 2 seconds later in main queue")
}

/// EZSwiftExtensions
func runThisAfterDelay(seconds seconds: Double, after: () -> ()) {
    let time = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
    dispatch_after(time, dispatch_get_main_queue(), after)
}

Its included as a standard function in my repo.

Jagat Dave
  • 1,643
  • 3
  • 23
  • 30
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
1

You can either wrap the argument in your own class, or wrap the method call in a method that doesn't need to be passed in the primitive type. Then call that method after your delay, and within that method perform the selector you wish to perform.

GendoIkari
  • 11,734
  • 6
  • 62
  • 104
1

Swift 3 & Xcode 8.3.2

This code will help you, i add an explanation too

// Create custom class, this will make your life easier
class CustomDelay {

    static let cd = CustomDelay()

    // This is your custom delay function
    func runAfterDelay(_ delay:Double, closure:@escaping ()->()) {
        let when = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
    }
}


// here how to use it (Example 1)
class YourViewController: UIViewController {

    // example delay time 2 second
    let delayTime = 2.0

    override func viewDidLoad() {
        super.viewDidLoad()

        CustomDelay.cd.runAfterDelay(delayTime) {
            // This func will run after 2 second
            // Update your UI here, u don't need to worry to bring this to the main thread because your CustomDelay already make this to main thread automatically :)
            self.runFunc()
        }
    }

    // example function 1
    func runFunc() {
        // do your method 1 here
    }
}

// here how to use it (Example 2)
class YourSecondViewController: UIViewController {

    // let say you want to user run function shoot after 3 second they tap a button

    // Create a button (This is programatically, you can create with storyboard too)
    let shootButton: UIButton = {
        let button = UIButton(type: .system)
        button.frame = CGRect(x: 15, y: 15, width: 40, height: 40) // Customize where do you want to put your button inside your ui
        button.setTitle("Shoot", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        // create an action selector when user tap shoot button
        shootButton.addTarget(self, action: #selector(shoot), for: .touchUpInside)   
    }

    // example shoot function
    func shoot() {
        // example delay time 3 second then shoot
        let delayTime = 3.0

        // delay a shoot after 3 second
        CustomDelay.cd.runAfterDelay(delayTime) {
            // your shoot method here
            // Update your UI here, u don't need to worry to bring this to the main thread because your CustomDelay already make this to main thread automatically :)
        }
    }   
}
0

I believe the author is not asking how to wait for a fractional time (delay), but instead how to pass a scalar as argument of the selector (withObject:) and the fastest way in modern objective C is:

[obj performSelector:...  withObject:@(0.123123123) afterDelay:10]

your selector have to change its parameter to NSNumber, and retrieve the value using a selector like floatValue or doubleValue

Andre
  • 1,135
  • 9
  • 20