How to create a delay in Swift?

2019-01-07 02:59发布

问题:

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.

回答1:

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:

... {
    sleep(4)
}

This uses the sleep function from UNIX.



回答2:

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

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
    // 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
}


回答3:

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

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.

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.



回答4:

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 Carthage then use it exactly like in the examples above:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}


回答5:

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")
        }


回答6:

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.



回答7:

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
}


回答8:

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
    }
}


回答9:

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.



回答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
})


回答11:

this is the simplest

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