如何在Swift中创建延迟?


Answers:


270

如果要从UI线程调用该方法,则可以考虑使用NSTimer或调度计时器,而不是进行睡眠(这会锁定您的程序)。

但是,如果您确实需要延迟当前线程:

do {
    sleep(4)
}

这使用sleepUNIX中的功能。


4
睡眠无需导入达尔文即可工作,不确定这是否是更改
Dan Beaulieu

17
如果您需要1秒分辨率以上的精度,usleep()也可以。
prewett '16

18
usleep()需花费百万分之一秒,因此usleep(1000000)会休眠1秒
哈里斯2016年

40
感谢您使“不要这样做”的序言简短。
Dan Rosenstark

2
我使用sleep()来模拟长时间运行的后台进程。
威廉·T·马拉德

345

dispatch_after在大多数情况下,使用块比将其sleep(time)用作执行睡眠的线程被阻止执行其他工作要好。当使用dispatch_after正在处理的线程时,不会被阻塞,因此它可以同时进行其他工作。
如果您正在处理应用程序的主线程,则使用sleep(time)将不利于应用程序的用户体验,因为在此期间UI不会响应。

在调度代码块的执行之后调度,而不是冻结线程:

迅捷≥3.0

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

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

1
定期行动呢?
2016年

1
有可能使用gcd进行定期任务,这在Apple开发人员文档的本文中进行了介绍,但我建议为此使用NSTimer。这个答案显示了如何快速准确地做到这一点。
Palle

2
针对Xcode 8.2.1更新的代码。先前.now() + .seconds(4)给出的错误:expression type is ambiguous without more context
理查兹

我如何暂停从队列中调用的函数?@Palle
Mukul更多2007年

14
您不再需要添加.now() + .seconds(5)它,只需.now() + 5
cnzac 17-10-16

45

Swift 3.0中不同方法之间的比较

1.睡觉

此方法没有回叫。将代码直接放在此行之后,在4秒钟内执行。它将使用户无法使用UI元素(例如测试按钮)进行迭代,直到时间消失。尽管当睡眠开始时按钮是冻结的,但活动指示器之类的其他元素仍在旋转而没有冻结。您无法在睡眠期间再次触发此操作。

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

在此处输入图片说明

2.调度,执行和计时

这三种方法的工作方式相似,它们都在具有回调的后台线程上运行,只是语法不同且功能略有不同。

Dispatch通常用于在后台线程上运行某些内容。它具有回调作为函数调用的一部分

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

Perform实际上是一个简化的计时器。它设置带有延迟的计时器,然后通过选择器触发功能。

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

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

最后,计时器还提供了重复回调的功能,在这种情况下这没有用

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


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

对于所有这三种方法,当您单击按钮触发它们时,UI都不会冻结,您可以再次单击它。如果再次单击该按钮,则会设置另一个计时器,并且回调将被触发两次。

在此处输入图片说明

结论

仅靠它们自己,这四种方法都无法令人满意。sleep将禁用用户交互,因此屏幕“冻结 ”(实际上不是)并导致不良的用户体验。其他三种方法不会冻结屏幕,但是您可以多次触发它们,并且在大多数情况下,您要等到回拨后才能允许用户再次拨打电话。

因此,更好的设计将是使用三种异步方法之一进行屏幕屏蔽。当用户单击按钮时,用半透明的视图覆盖整个屏幕,顶部有一个旋转活动指示器,告诉用户正在处理按钮单击。然后删除回调函数中的视图和指示符,告诉用户该操作已正确处理,等等。


1
请注意,在Swift 4中,关闭objc推断后,您需要@objc在该perform(...)选项的回调函数之前添加。像这样:@objc func callback() {
leanne

32

我同意Palle的观点,在这里使用dispatch_after一个不错的选择。但是您可能不喜欢GCD的调用,因为它们很烦人。相反,您可以添加此方便的助手

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

现在,您只需在后台线程上延迟代码,如下所示:

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

主线程上延迟代码甚至更简单:

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

如果您更喜欢具有更多便捷功能的Framework,请签出HandySwift。您可以通过CarthageAccio将其添加到项目中然后像上面的示例一样使用它:

import HandySwift    

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

这似乎很有帮助。您介意使用迅速的3.0版本对其进行更新吗?谢谢
grahamcracker1234 '16

我开始将HandySwift迁移到Swift 3,并计划下周发布新版本。然后我还要更新上面的脚本。敬请关注。
Jeehut

HandySwift到Swift 3的迁移现已完成,并在1.3.0版中发布。我还刚刚更新了上面的代码以与Swift 3一起使用!:)
Jeehut

1
为什么要乘以NSEC_PER_SEC然后又除以NSEC?那不是多余的吗?(例如Double(Int64(seconds * Double(NSEC_PER_SEC))/ Double(NSEC_PER_SEC)))您是否只写'DispatchTime.now()+ Double(seconds)?
Mark A. Donohoe

1
谢谢@MarqueIV,您当然正确。我刚刚更新了代码。这仅仅是从Xcode 8自动转换的结果,之前需要类型转换,而DispatchTimeSwift 2中没有let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
Jeehut

24

您也可以使用Swift 3进行此操作。

这样延迟后执行功能。

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

23

在Swift 4.2和Xcode 10.1中

您总共有4种方法可以延迟。在这些选项1中,最好在一段时间后调用或执行一个函数。的睡眠()是在使用至少情况。

选项1。

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

}

选项2。

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

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

选项3。

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

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

}

选项4。

sleep(5)

如果您想在一段时间后调用某个函数来执行某些操作,请不要使用sleep


19

NSTimer

@nneonneo的答案建议使用,NSTimer但没有显示如何做。这是基本语法:

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

这是一个非常简单的项目,展示了如何使用它。当按下按钮时,它将启动一个计时器,该计时器将在半秒钟的延迟后调用一个函数。

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

使用dispatch_time(如Palle的答案)是另一个有效的选择。但是,很难取消。使用NSTimer,要在发生延迟事件之前将其取消,您所需要做的就是致电

timer.invalidate()

使用 sleep不推荐,尤其是在主线程,因为它停止的线程上正在做的所有工作。

请参阅此处以获取更完整的答案。


7

在Swift 3.0中尝试以下实现

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

用法

delayWithSeconds(1) {
   //Do something
}

7

您可以创建扩展以轻松使用延迟功能(语法: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)
    }
}

如何在UIViewController中使用

self.delay(0.1, closure: {
   //execute code
})

6

如果需要将延迟设置为小于一秒,则无需设置.seconds参数。我希望这对某人有用。

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

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

4
做起来DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}可能更正确✌️–
eonist

5

如果您的代码已经在后台线程中运行,请在Foundation中使用此方法暂停线程:Thread.sleep(forTimeInterval:)

例如:

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

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

(当代码在主线程上运行时,请参阅其他答案以获取建议。)


3

要创建简单的时间延迟,可以导入Darwin,然后使用sleep(seconds)进行延迟。不过,这只需要整整几秒钟,因此对于更精确的测量,您可以导入Darwin并使用usleep(百万分之一秒)进行非常精确的测量。为了测试这一点,我写道:

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

哪个打印,然后等待1秒钟并打印,然后等待0.4秒钟然后打印。所有工作均按预期进行。


-3

这是最简单的

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

2
这不是Foundation的一部分,但我想它已包含在“ HandySwift” Cocoapod中。您应该在回答中加以说明。
Jan Nash

swift是否有等待发生的东西?
Saeed Rahmatolahi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.