简体   繁体   English

如何在 Swift 中创建延迟?

[英]How to create a delay in Swift?

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.换句话说,我希望我的应用程序执行代码,但在某个时间点暂停 4 秒,然后继续执行代码的 rest。 How can I do this?我怎样才能做到这一点?

I am using Swift.我正在使用 Swift。

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.在大多数情况下,使用dispatch_after块比使用sleep(time)更好,因为执行睡眠的线程被阻止执行其他工作。 when using dispatch_after the thread which is worked on does not get blocked so it can do other work in the meantime.使用dispatch_after时,正在处理的线程不会被阻塞,因此它可以同时执行其他工作。
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.如果您在应用程序的主线程上工作,则使用sleep(time)对应用程序的用户体验不利,因为在此期间 UI 没有响应。

Dispatch after schedules the execution of a block of code instead of freezing the thread: Dispatch after 调度代码块的执行而不是冻结线程:

Swift ≥ 3.0斯威夫特≥3.0

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

Swift < 3.0斯威夫特 < 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
}

Instead of a sleep, which will lock up your program if called from the UI thread, consider using NSTimer or a dispatch timer.考虑使用NSTimer或调度计时器,而不是在从 UI 线程调用时会锁定您的程序的睡眠。

But, if you really need a delay in the current thread:但是,如果您真的需要在当前线程中延迟:

do {
    sleep(4)
}

This uses the sleep function from UNIX.这使用了 UNIX 的sleep功能。

Comparison between different approaches in swift 3.0 swift 3.0 中不同方法的比较

1. Sleep 1. 睡觉

This method does not have a call back.此方法没有回调。 Put codes directly after this line to be executed in 4 seconds.将代码直接放在这一行之后,在 4 秒内执行。 It will stop user from iterating with UI elements like the test button until the time is gone.它将阻止用户使用测试按钮等 UI 元素进行迭代,直到时间结束。 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 2. 调度、执行和计时器

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. Dispatch 通常用于在后台线程上运行一些东西。 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. Perform 实际上是一个简化的计时器。 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.对于这三种方法,当您点击按钮触发它们时,UI 不会冻结,您可以再次点击它。 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. sleep将禁用用户交互,因此屏幕“冻结”(实际上并非如此)并导致糟糕的用户体验。 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.然后在回调函数中删除视图和指示器,告诉用户该操作已正确处理等。

In Swift 4.2 and Xcode 10.1在 Swift 4.2 和 Xcode 10.1 中

You have 4 ways total to delay.您总共有 4 种方法可以延迟。 Out of these option 1 is preferable to call or execute a function after some time.在这些选项 1中,最好在一段时间后调用或执行函数。 The sleep() is least case in use. sleep()是最少使用的情况。

Option 1.选项1。

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

}

Option 2.选项 2。

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

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

Option 3.选项 3。

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

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

}

Option 4.选项 4。

sleep(5)

If you want to call a function after some time to execute something don't use sleep .如果您想在一段时间后调用函数来执行某些操作,请不要使用sleep

I agree with Palle that using dispatch_after is a good choice here.我同意 Palle 的观点,在这里使用dispatch_after一个不错的选择 But you probably don't like the GCD calls as they are quite annoying to write .但是你可能不喜欢 GCD 调用,因为它们写起来烦人 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 .如果您更喜欢具有更多方便功能的框架,请查看 HandySwift You can add it to your project via SwiftPM then use it exactly like in the examples above:你可以通过SwiftPM将它添加到你的项目中然后像上面的例子一样使用它:

import HandySwift    

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

You can also do this with Swift 3.您也可以使用 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")
        }

NSTimer 定时器

The answer by @nneonneo suggested using NSTimer but didn't show how to do it. @nneonneo 的答案建议使用NSTimer但没有说明如何去做。 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.使用dispatch_time (如Palle 的回答)是另一个有效的选择。 However, it is hard to cancel .但是,很难取消 With NSTimer , to cancel a delayed event before it happens, all you need to do is call使用NSTimer ,要在延迟事件发生之前取消它,您需要做的就是调用

timer.invalidate()

Using sleep is not recommended, especially on the main thread, since it stops all the work being done on the thread.不推荐使用sleep ,特别是在主线程上,因为它会停止在线程上完成的所有工作。

See here for my fuller answer.在这里查看我更完整的答案。

Try the following implementation in Swift 3.0在 Swift 3.0 中尝试以下实现

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

Usage用法

delayWithSeconds(1) {
   //Do something
}

If you need to set a delay of less than a second, it is not necessary to set the .seconds parameter.如果需要设置小于一秒的延迟,则不需要设置 .seconds 参数。 I hope this is useful to someone.我希望这对某人有用。

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

You can create extension to use delay function easily (Syntax: Swift 4.2+)您可以创建扩展以轻松使用延迟功能(语法: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如何在 UIViewController 中使用

self.delay(0.1, closure: {
   //execute code
})
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
    }
}

If your code is already running in a background thread, pause the thread using this method in Foundation : Thread.sleep(forTimeInterval:)如果您的代码已经在后台线程中运行,请使用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.) (当您的代码在主线程上运行时,请参阅其他答案以获取建议。)

To create a simple time delay, you can import Darwin and then use sleep(seconds) to do the delay.要创建一个简单的时间延迟,您可以导入 Darwin,然后使用 sleep(seconds) 进行延迟。 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.不过,这只需要整整几秒钟,因此要进行更精确的测量,您可以导入 Darwin 并使用 usleep(百万分之一秒)进行非常精确的测量。 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.哪个打印,然后等待 1 秒并打印,然后等待 0.4 秒然后打印。 All worked as expected.一切都按预期工作。

I believe the simplest and latest way of doing a 4 seconds timer is:我相信做 4 秒计时器的最简单和最新的方法是:

Task { 
    // Do something

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

}

It uses Swift 5.5's new concurrency.它使用Swift 5.5 的新并发。

苹果的同意

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:作为之前提出的选项的替代解决方案,您可以使用基于DispatchGroup类的延迟,该类旨在同步多个异步任务的执行:

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.其中enter()方法用于明确表示组代码的执行已经开始, wait(timeout:)方法用于等待组任务完成。 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 :使用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.不幸的是,我不能说这个延迟的准确度是多少, wait(timeout:)方法允许程序在指定延迟之后进一步执行的概率是多少。

Also, this solution allows you to delay the code in the current queue, without having to execute it in a separate closure.此外,该解决方案允许您延迟当前队列中的代码,而不必在单独的闭包中执行它。

Using DispatchQueue 's .asyncAfter method you can execute code after given time.使用DispatchQueue.asyncAfter方法,您可以在给定时间后执行代码。 So, eg execute ... on main thread after 1 second looks like this:因此,例如在 1 秒后在主线程上执行...看起来像这样:

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

Using my handy Delay wrapper struct you can execute it in more fancy way:使用我方便的Delay包装结构,您可以以更奇特的方式执行它:

struct Delay {

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

}

Usage:用法:

Delay(0.4) { ... }

Swift 5< Swift 5<

Using Task.sleep will not block any code other than the task at hand, and it's pretty straightforward.使用Task.sleep不会阻塞除手头任务之外的任何代码,而且非常简单。

//Delay task by 4 seconds:

Task {
    try await Task.sleep(nanoseconds: 4000000000)
    //Execute your code here
}

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
}

this is the simplest这是最简单的

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM