388

I want to pause my app at a certain in point. In other words, I want my app to execute the code, but then at a certain point, pause for 4 seconds, and then continue on with the rest of the code. How can I do this?

I am using Swift.

Naresh
  • 16,698
  • 6
  • 112
  • 113
Schuey999
  • 4,706
  • 7
  • 21
  • 36

19 Answers19

498

Using a dispatch_after block is in most cases better than using sleep(time) as the thread on which the sleep is performed is blocked from doing other work. when using dispatch_after the thread which is worked on does not get blocked so it can do other work in the meantime.
If you are working on the main thread of your application, using sleep(time) is bad for the user experience of your app as the UI is unresponsive during that time.

Dispatch after schedules the execution of a block of code instead of freezing the thread:

Swift ≥ 3.0

let seconds = 4.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
    // Put your code which should be executed with a delay here
}

Swift ≥ 5.5 in an async context:

func foo() async {
    try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))
    // Put your code which should be executed with a delay here
}

Swift < 3.0

let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 4 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
    // Put your code which should be executed with a delay here
}
Palle
  • 11,511
  • 2
  • 40
  • 61
  • 1
    What about periodic actions? – qed Jan 18 '16 at 02:00
  • 2
    there are possibilities to use gcd for periodic tasks which are described in [this article from the apple developer documentation](https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html#//apple_ref/doc/uid/TP40008091-CH103-SW2) but i would recommend using a NSTimer for that. [this answer](http://stackoverflow.com/a/24007862/2557145) shows how to do exactly that in swift. – Palle Jan 18 '16 at 09:16
  • 2
    Code updated for Xcode 8.2.1. Previously `.now() + .seconds(4)` gives error: `expression type is ambiguous without more context` – richards Feb 12 '17 at 08:24
  • How can i suspend the function invoked from the queue?@Palle – Mukul More Jul 17 '17 at 09:28
  • @MukulMore you can't. Once scheduled, the task will be executed. You need to check manually if your task should continue to run. – Palle Jul 17 '17 at 10:17
  • I created a new operation queue to manage it added the functions to its execution block and suspend it when required – Mukul More Jul 17 '17 at 13:12
  • 14
    You no longer need to add `.now() + .seconds(5)` it is simply `.now() + 5` – cnzac Oct 16 '17 at 03:23
  • @cnzac it was never required. – Palle Oct 16 '17 at 08:24
  • @Palle For sure, but the answer doesn't work in Xcode when using `+ .seconds(5)` – cnzac Oct 16 '17 at 17:43
  • @cnzac works for me and has always worked (Currently on Swift 4.0.1) What exact error message are you facing? What environment are you on? You could try it with `+ DispatchTimeInterval.seconds(4)` – Palle Oct 16 '17 at 21:10
  • I'm in Xcode 9, it works fine as long as I just use `.now() + 5` instead of `.now() + .seconds(5)`. I get this error otherwise: `gives error: expression type is ambiguous without more context` – cnzac Oct 18 '17 at 00:12
  • Can you reproduce this in an newly created, empty project? – Palle Oct 18 '17 at 02:12
  • an async call and sleep are completely distinct things and have different use cases. I don't think this is an appropriate answer as it won't block the current thread and that can be a problem. – keios Jul 07 '21 at 18:06
  • @keios there are hardly any situations when programming GUI applications in which you would prefer `sleep()` over a non-blocking alternative. Also, using `sleep` on global dispatch queues is discouraged and may have performance implications (see [this thread on the Swift forums](https://forums.swift.org/t/sleep-in-dispatchqueue/19763)) – Palle Oct 18 '21 at 08:28
369

Instead of a sleep, which will lock up your program if called from the UI thread, consider using NSTimer or a dispatch timer.

But, if you really need a delay in the current thread:

do {
    sleep(4)
}

This uses the sleep function from UNIX.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 4
    sleep works without importing darwin, not sure if this is a change – Dan Beaulieu Aug 20 '15 at 13:40
  • 23
    usleep() works, too, if you need something more precise that 1 sec resolution. – prewett Feb 20 '16 at 02:50
  • 25
    usleep() takes millionths of a second, so usleep(1000000) will sleep for 1 sec – Elijah Nov 23 '16 at 04:36
  • 66
    Thanks for keeping the "don't do this" preamble short. – Dan Rosenstark Mar 20 '17 at 20:09
  • 4
    I use sleep() to simulate long-running background processes. – William T. Mallard Jan 25 '18 at 15:57
  • 2
    I prefer Thread.sleep(forTimeInterval: 4.0) which lets you specify the duration with a regular TimeInterval. – Robin Stewart Mar 08 '19 at 20:15
  • @Dmitry: [The documentation](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sleep.3.html) explicitly says "suspends execution of the **calling thread**" (emphasis mine), and this is the way it's [specified to work in UNIX](https://pubs.opengroup.org/onlinepubs/007908799/xsh/sleep.html). – nneonneo Dec 20 '19 at 23:48
68

Comparison between different approaches in swift 3.0

1. Sleep

This method does not have a call back. Put codes directly after this line to be executed in 4 seconds. It will stop user from iterating with UI elements like the test button until the time is gone. Although the button is kind of frozen when sleep kicks in, other elements like activity indicator is still spinning without freezing. You cannot trigger this action again during the sleep.

sleep(4)
print("done")//Do stuff here

enter image description here

2. Dispatch, Perform and Timer

These three methods work similarly, they are all running on the background thread with call backs, just with different syntax and slightly different features.

Dispatch is commonly used to run something on the background thread. It has the callback as part of the function call

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
    print("done")
})

Perform is actually a simplified timer. It sets up a timer with the delay, and then trigger the function by selector.

perform(#selector(callback), with: nil, afterDelay: 4.0)

func callback() {
    print("done")
}}

And finally, timer also provides ability to repeat the callback, which is not useful in this case

Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(callback), userInfo: nil, repeats: false)


func callback() {
    print("done")
}}

For all these three method, when you click on the button to trigger them, UI will not freeze and you are allowed to click on it again. If you click on the button again, another timer is set up and the callback will be triggered twice.

enter image description here

In conclusion

None of the four method works good enough just by themselves. sleep will disable user interaction, so the screen "freezes"(not actually) and results bad user experience. The other three methods will not freeze the screen, but you can trigger them multiple times, and most of the times, you want to wait until you get the call back before allowing user to make the call again.

So a better design will be using one of the three async methods with screen blocking. When user click on the button, cover the entire screen with some translucent view with a spinning activity indicator on top, telling user that the button click is being handled. Then remove the view and indicator in the call back function, telling user that the the action is properly handled, etc.

Fangming
  • 24,551
  • 6
  • 100
  • 90
  • 4
    Note that in Swift 4, with objc inference turned off, you'll need to add `@objc` in front of the callback function for the `perform(...)` option. Like so: `@objc func callback() {` – leanne Jun 18 '18 at 22:09
48

In Swift 4.2 and Xcode 10.1

You have 4 ways total to delay. Out of these option 1 is preferable to call or execute a function after some time. The sleep() is least case in use.

Option 1.

DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    self.yourFuncHere()
}
//Your function here    
func yourFuncHere() {

}

Option 2.

perform(#selector(yourFuncHere2), with: nil, afterDelay: 5.0)

//Your function here  
@objc func yourFuncHere2() {
    print("this is...")
}

Option 3.

Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(yourFuncHere3), userInfo: nil, repeats: false)

//Your function here  
@objc func yourFuncHere3() {

}

Option 4.

sleep(5)

If you want to call a function after some time to execute something don't use sleep.

N.W
  • 672
  • 2
  • 8
  • 19
Naresh
  • 16,698
  • 6
  • 112
  • 113
40

I agree with Palle that using dispatch_after is a good choice here. But you probably don't like the GCD calls as they are quite annoying to write. Instead you can add this handy helper:

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 a background thread like this:

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

Delaying code on the main thread is even simpler:

delay(bySeconds: 1.5) { 
    // delayed code, by default run in main thread
}

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

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}
Jeehut
  • 20,202
  • 8
  • 59
  • 80
  • This seem very helpful. Do you mind updating it with a swift 3.0 version. Thanks – grahamcracker1234 Aug 16 '16 at 20:51
  • I started migrating HandySwift to Swift 3 and plan to release a new version next week. Then I'm gonna update the script above, too. Stay tuned. – Jeehut Aug 18 '16 at 23:02
  • Migration of HandySwift to Swift 3 is now complete and released in version 1.3.0. I've also just updated the code above to work with Swift 3! :) – Jeehut Sep 20 '16 at 22:45
  • 1
    Why are you multiplying by NSEC_PER_SEC then dividing by it again? Isn't that redundant? (e.g. Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) Couldn't you just write 'DispatchTime.now() + Double(seconds)? – Mark A. Donohoe Nov 15 '16 at 21:45
  • 1
    Thank you @MarqueIV, you are right of course. I just updated the code. It was simply the result of the automatic conversion from Xcode 8 and type conversion was needed before as `DispatchTime` wasn't available in Swift 2. It was `let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))` before. – Jeehut Nov 21 '16 at 04:32
  • hi buddy, can i call this method in background(app not running) after 30 seconds? Thanks in advance. – famfamfam Mar 08 '21 at 13:56
25

You can also do this with Swift 3.

Perform the function after delay like so.

override func viewDidLoad() {
    super.viewDidLoad()

    self.perform(#selector(ClassName.performAction), with: nil, afterDelay: 2.0)
}


     @objc func performAction() {
//This function will perform after 2 seconds
            print("Delayed")
        }
Cyril
  • 2,783
  • 1
  • 24
  • 35
22

NSTimer

The answer by @nneonneo suggested using NSTimer but didn't show how to do it. This is the basic syntax:

let delay = 0.5 // time in seconds
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(myFunctionName), userInfo: nil, repeats: false)

Here is a very simple project to show how it might be used. When a button is pressed it starts a timer that will call a function after a delay of half a second.

import UIKit
class ViewController: UIViewController {

    var timer = NSTimer()
    let delay = 0.5
    
    // start timer when button is tapped
    @IBAction func startTimerButtonTapped(sender: UIButton) {

        // cancel the timer in case the button is tapped multiple times
        timer.invalidate()

        // start the timer
        timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
    }

    // function to be called after the delay
    func delayedAction() {
        print("action has started")
    }
}

Using dispatch_time (as in Palle's answer) is another valid option. However, it is hard to cancel. With NSTimer, to cancel a delayed event before it happens, all you need to do is call

timer.invalidate()

Using sleep is not recommended, especially on the main thread, since it stops all the work being done on the thread.

See here for my fuller answer.

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
13

I believe the simplest and latest way of doing a 4 seconds timer is:

Task { 
    // Do something

    // Wait for 4 seconds
    try await Task.sleep(nanoseconds: 4_000_000_000) 

}

It uses Swift 5.5's new concurrency.

Apple's Concurren

Legolas Wang
  • 1,951
  • 1
  • 13
  • 26
11

Try the following implementation in Swift 3.0

func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { 
        completion()
    }
}

Usage

delayWithSeconds(1) {
   //Do something
}
Vakas
  • 6,291
  • 3
  • 35
  • 47
10

If you need to set a delay of less than a second, it is not necessary to set the .seconds parameter. I hope this is useful to someone.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})
Booharin
  • 753
  • 10
  • 10
10

You can create extension to use delay function easily (Syntax: Swift 4.2+)

extension UIViewController {
    func delay(_ delay:Double, closure:@escaping ()->()) {
        DispatchQueue.main.asyncAfter(
            deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
    }
}

How to use in UIViewController

self.delay(0.1, closure: {
   //execute code
})
Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
8

If your code is already running in a background thread, pause the thread using this method in Foundation: Thread.sleep(forTimeInterval:)

For example:

DispatchQueue.global(qos: .userInitiated).async {

    // Code is running in a background thread already so it is safe to sleep
    Thread.sleep(forTimeInterval: 4.0)
}

(See other answers for suggestions when your code is running on the main thread.)

Robin Stewart
  • 3,147
  • 20
  • 29
6
DispatchQueue.global(qos: .background).async {
    sleep(4)
    print("Active after 4 sec, and doesn't block main")
    DispatchQueue.main.async{
        //do stuff in the main thread here
    }
}
Sentry.co
  • 5,355
  • 43
  • 38
  • 5
    Doing `DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}` Is probably more correct ✌️ – Sentry.co Oct 17 '18 at 10:44
5

To create a simple time delay, you can import Darwin and then use sleep(seconds) to do the delay. That only takes whole seconds, though, so for more precise measurements you can import Darwin and use usleep(millionths of a second) for very precise measurement. To test this, I wrote:

import Darwin
print("This is one.")
sleep(1)
print("This is two.")
usleep(400000)
print("This is three.")

Which prints, then waits for 1 sec and prints, then waits for 0.4 sec then prints. All worked as expected.

Ethan Wrightson
  • 149
  • 1
  • 5
  • 12
4

Edit:

I strongly recommend start using Swift Concurrency. With Swift Concurrency you can easily pause current Task using Task.sleep.


Using DispatchQueue's .asyncAfter method you can execute code after given time. So, e.g. execute ... on main thread after 1 second looks like this:

DispatchQueue.main.asyncAfter(deadline: .now() + 1) { ... }

Using my handy Delay wrapper struct you can execute it in more fancy way:

struct Delay {

    @discardableResult
    init(_ timeInterval: TimeInterval, queue: DispatchQueue = .main, executingBlock: @escaping () -> Void) {
        queue.asyncAfter(deadline: .now() + timeInterval, execute: executingBlock)
    }

}

Usage:

Delay(0.4) { ... }
Robert Dresler
  • 10,580
  • 2
  • 22
  • 40
3

Swift 5<

Using Task.sleep will not block any code other than the task at hand, and it's pretty straightforward.

//Delay task by 4 seconds:

Task {
    try await Task.sleep(nanoseconds: 4000000000)
    //Execute your code here
}
Jorge CF
  • 41
  • 1
2

This is a simpler way of adding a delay that doesn't affect thread execution.

let continueTime: Date = Calendar.current.date(byAdding: .second, value: 30, to: Date())!
while (Date() < continueTime) {
    //DO NOTHING
}
Logan
  • 57
  • 4
1

As an alternative solution to the previously proposed options, you can use a delay based on the DispatchGroup class, which is designed to synchronise the execution of several asynchronous tasks:

print("Start")
print(Date())

let delay = DispatchTimeInterval.seconds(3)
let group = DispatchGroup()
group.enter()
_ = group.wait(timeout: .now() + delay)

print("Finish")
print(Date())

Where the enter() method is used to explicitly indicate that the execution of the group code has begun and wait(timeout:) method to wait for group tasks to complete. Of course, in this example this will never happen, for this a timeout is specified, which is equal to the required delay.

It is convenient to use it as a ready-made helper:

public class DispatchWait {
    private init () { }
    
    public static func `for` (_ interval: DispatchTimeInterval) {
        let group = DispatchGroup()
        group.enter()
        _ = group.wait(timeout: .now().advanced(by: interval))
    }
}

An example of using the DispatchWait:

print("Start")
print(Date())

DispatchWait.for(.seconds(3))

print("Finish")
print(Date())

Unfortunately, I cannot say what is the accuracy of this delay, and what is the probability that the wait(timeout:) method will allow further execution of the program much later than the specified delay.

Also, this solution allows you to delay the code in the current queue, without having to execute it in a separate closure.

Asio Otus
  • 71
  • 6
-5

this is the simplest

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })
taha
  • 23
  • 1