480

In Swift 2, I was able to use dispatch_after to delay an action using grand central dispatch:

var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))) 
dispatch_after(dispatchTime, dispatch_get_main_queue(), { 
    // your function here 
})

But this no longer seems to compile since Swift 3. What is the preferred way to write this in modern Swift?

Naresh
  • 16,698
  • 6
  • 112
  • 113
brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • 6
    Further information about the migration process can be found here: [https://swift.org/migration-guide/](https://swift.org/migration-guide/) The section "Dispatch" is the relevant for this question – tonik12 Jun 14 '16 at 12:53
  • should your question be `UInt64`? – mfaani Apr 10 '17 at 20:48

9 Answers9

1183

The syntax is simply:

// to run something in 0.1 seconds

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    // your code here
}

Note, the above syntax of adding seconds as a Double seems to be a source of confusion (esp since we were accustomed to adding nsec). That “add seconds as Double” syntax works because deadline is a DispatchTime and, behind the scenes, there is a + operator that will take a Double and add that many seconds to the DispatchTime:

public func +(time: DispatchTime, seconds: Double) -> DispatchTime

But, if you really want to add an integer number of msec, μs, or nsec to the DispatchTime, you can also add a DispatchTimeInterval to a DispatchTime. That means you can do:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
    // 500 msec, i.e. 0.5 seconds
    …
}

DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(1_000_000)) {
    // 1m microseconds, i.e. 1 second
    …
}

DispatchQueue.main.asyncAfter(deadline: .now() + .nanoseconds(1_500_000_000)) {
    // 1.5b nanoseconds, i.e. 1.5 seconds
    …
}

These all seamlessly work because of this separate overload method for the + operator in the DispatchTime class.

public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime

It was asked how one goes about canceling a dispatched task. To do this, use DispatchWorkItem. For example, this starts a task that will fire in five seconds, or if the view controller is dismissed and deallocated, its deinit will cancel the task:

class ViewController: UIViewController {

    private var item: DispatchWorkItem?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        item = DispatchWorkItem { [weak self] in
            self?.doSomething()
            self?.item = nil
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: item!)
    }

    deinit {
        item?.cancel()
    }
    
    func doSomething() { … }
}

Note the use of the [weak self] capture list in the DispatchWorkItem. This is essential to avoid a strong reference cycle. Also note that this does not do a preemptive cancelation, but rather just stops the task from starting if it hasn’t already. But if it has already started by the time it encounters the cancel() call, the block will finish its execution (unless you’re manually checking isCancelled inside the block).


Swift concurrency

While the original question was about the old GCD dispatch_after vs. the newer asyncAfter API, this raises the question of how to achieve the same behavior in the newer Swift concurrency and its async-await. As of iOS 16 and macOS 13 we would prefer Task.sleep(for:):

try await Task.sleep(for: .seconds(2))        // 2 seconds
…

Or

try await Task.sleep(for: .milliseconds(200)) // 0.2 seconds
…

Or if we need to support back to iOS 13 and macOS 10.15, we would use Task.sleep(nanoseconds:) instead.

And to support cancelation, save a Task:

class ViewController: UIViewController {

    private var task: Task<Void, Error>?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        task = Task {
            try await Task.sleep(for: .seconds(5))
            await doSomething()
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        task?.cancel()
    }

    func doSomething() async { … }
}

Or in SwiftUI, we might use the .task {…} view modifier, which “will automatically cancel the task at some point after the view disappears before the action completes.” We do not even need to save the Task to manually cancel later.

We should recognize that before we had Swift concurrency, calling any sleep function used to be an anti-pattern, one that we would studiously avoid, because it would block the current thread. That was a serious error when done from the main thread, but even was problematic when used in background threads as the GCD worker thread pool is so limited. But the new Task.sleep functions do not block the current thread, and are therefore safe to use from any actor (including the main actor).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 7
    Thanks for pointing that out, and in fact https://swift.org/migration-guide/ mentions the need to make that change by hand. – matt Jun 14 '16 at 15:36
  • 1
    Oh, sorry. It's way too late here :). Thought that all the mess should go actually, but didn't take the leap. IMO the "simple" solution is the-one-true-solution. – tobiasdm Jun 15 '16 at 02:02
  • 1
    @Rob how would I go about canceling it? Thanks. – kemicofa ghost Oct 04 '16 at 09:12
  • Ok so how do you add a dynamic wait? For example, I have a let number : Float = 1.0. And .now() + .milliseconds(number) doesn't work. Nor does Double(number). I can't figure it out. – Kjell Nov 03 '16 at 01:25
  • 2
    The `DispatchTimeInterval` renditions, like `.milliseconds` require `Int`. But if just adding seconds, I'd use `Double`, e.g. `let n: Double = 1.0; queue.asyncAfter(deadline: .now() + n) { ... }`. – Rob Nov 10 '16 at 17:01
  • Here, 5 is in seconds or milliseconds? – Jayprakash Dubey Mar 11 '20 at 07:52
  • @JayprakashDubey - If you say `.now() + 5`, that’s five seconds. If you want Anything other than seconds, (e.g. millseconds or nanoseconds or anything else), you have to explicitly qualify it (e.g. `.now() + .milliseconds(5)`). – Rob Mar 11 '20 at 09:27
132

Swift 4:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
   // Code
}

For the time .seconds(Int), .microseconds(Int) and .nanoseconds(Int) may also be used.

Sverrisson
  • 17,970
  • 5
  • 66
  • 62
63

If you just want the delay function in

Swift 4 & 5

func delay(interval: TimeInterval, closure: @escaping () -> Void) {
     DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
          closure()
     }
}

You can use it like:

delay(interval: 1) { 
    print("Hi!")
}
rockdaswift
  • 9,613
  • 5
  • 40
  • 46
16

after Swift 3 release, also the @escaping has to be added

func delay(_ delay: Double, closure: @escaping () -> ()) {
  DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
    closure()
  }
}
Marco Pappalardo
  • 935
  • 6
  • 23
5

Swift 4

You can create a extension on DispatchQueue and add function delay which uses DispatchQueue asyncAfter function internally

extension DispatchQueue {
   static func delay(_ delay: DispatchTimeInterval, closure: @escaping () -> ()) {
      DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure)
   }
}

and use

DispatchQueue.delay(.milliseconds(10)) {
   print("task to be done")
}
Suhit Patil
  • 11,748
  • 3
  • 50
  • 60
  • 2
    How is this different from @rockdaswift’s answer? – brandonscript Jun 24 '17 at 16:09
  • as I mentioned it wraps asyncAfter inside performAfter function which takes delay as parameter and it can be easier to call using just performAfter(delay: 2) { } – Suhit Patil Jun 24 '17 at 16:19
  • Closure parameters are non-escaping by default, @escaping indicate that a closure parameter may escape. added @ escaping parameter in closure to save potential crash. – Suhit Patil Jun 24 '17 at 16:59
5

A somewhat different flavour of the Accepted Answer.

Swift 4

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 + .milliseconds(500) + 
.microseconds(500) + .nanoseconds(1000)) {
                print("Delayed by 0.1 second + 500 milliseconds + 500 microseconds + 
                      1000 nanoseconds)")
 }
Md. Ibrahim Hassan
  • 5,359
  • 1
  • 25
  • 45
3

call DispatchQueue.main.after(when: DispatchTime, execute: () -> Void)

I'd highly recommend using the Xcode tools to convert to Swift 3 (Edit > Convert > To Current Swift Syntax). It caught this for me

jjatie
  • 5,152
  • 5
  • 34
  • 56
3

Swift 5 and above

DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
   // code to execute                 
})
midhun p
  • 1,987
  • 18
  • 24
1

None of the answers mentioned running on a non-main thread, so adding my 2 cents.

On main queue (main thread)

let mainQueue = DispatchQueue.main
let deadline = DispatchTime.now() + .seconds(10)
mainQueue.asyncAfter(deadline: deadline) {
    // ...
}

OR

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(10)) { 
    // ...
}

On global queue (non main thread, based on QOS specified) .

let backgroundQueue = DispatchQueue.global()
let deadline = DispatchTime.now() + .milliseconds(100)
backgroundQueue.asyncAfter(deadline: deadline, qos: .background) { 
    // ...
}

OR

DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + .milliseconds(100), qos: .background) {
    // ...
}
MANN
  • 3,360
  • 1
  • 17
  • 18