如何在Swift中创建延迟?

我想在某个点暂停我的应用程序。换句话说,我希望我的应用程序执行代码,但在某一点上,暂停4秒,然后继续执行其余的代码。我该怎么做呢?

我用的是Swift。

460215 次浏览

考虑使用NSTimer或分派计时器,而不是使用sleep(如果从UI线程调用将锁定程序)。

但是,如果你真的需要延迟当前线程:

do {
sleep(4)
}

它使用了UNIX中的sleep函数。

在大多数情况下,使用dispatch_after块比使用sleep(time)更好,因为执行睡眠的线程被阻止做其他工作。当使用dispatch_after时,被处理的线程不会被阻塞,因此它可以在此期间做其他工作。如果你在你的应用程序的主线程上工作,使用sleep(time)对你的应用程序的用户体验是不好的,因为UI在这段时间是无响应的 after调度调度代码块的执行,而不是冻结线程:

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(异步):

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
}

斯威夫特& lt;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
}

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,特别是在主线程中,因为它会停止线程上正在完成的所有工作。

我更完整的答案见在这里

我同意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
}

如果你更喜欢框架,它也有一些更方便的功能,那么签出HandySwift。你可以把它添加到你的项目via SwiftPM .中,然后像上面的例子一样使用它:

import HandySwift


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

这是最简单的

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

在Swift 3.0中尝试以下实现

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

使用

delayWithSeconds(1) {
//Do something
}

你也可以用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")
}

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

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

打印,然后等待1秒打印,然后等待0.4秒打印。一切都如预期般顺利。

swift 3.0中不同方法的比较

1. 睡眠

此方法没有回调。将代码直接放在这一行之后,在4秒内执行。它将阻止用户迭代UI元素,如测试按钮,直到时间结束。虽然按钮在睡眠开始时有点冻结,但其他元素,如活动指示器仍在旋转而没有冻结。在休眠期间不能再次触发此操作。

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

enter image description here

2. 调度,执行和定时器

这三种方法的工作原理类似,它们都运行在后台线程上,并带有回调,只是语法和功能略有不同。

分派通常用于在后台线程上运行一些东西。它有回调函数作为函数调用的一部分

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

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

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


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

最后,timer还提供了重复回调的能力,这在这个例子中是没有用的

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




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

对于这三种方法,当你点击按钮触发它们时,UI不会冻结,你可以再次点击它。如果你再次点击按钮,另一个计时器将被设置,回调将被触发两次。

enter image description here

总之

这四种方法单独使用都不够好。sleep将禁用用户交互,所以屏幕“冻结”(实际上不是)并导致糟糕的用户体验。其他三个方法不会冻结屏幕,但您可以多次触发它们,大多数情况下,您希望等待,直到您得到回调才允许用户再次进行调用。

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

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

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

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

在Swift 4.2和Xcode 10.1中

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

选项1。

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


}

第二个选项。

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)

如果你想在一段时间后调用一个函数来执行一些东西,不要使用睡眠

如果你的代码已经在后台线程中运行,使用本方法在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)
}

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

你可以创建扩展来轻松使用延迟函数(语法: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
})

作为前面提出的选项的替代解决方案,您可以使用基于DispatchGroup类的延迟,该类被设计用于同步多个异步任务的执行:

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


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


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

其中enter()方法用于显式地指示组代码的执行已经开始,而wait(timeout:)方法用于等待组任务完成。当然,在本例中,这种情况永远不会发生,为此指定了一个超时,它等于所需的延迟。

使用它作为现成的帮手非常方便:

public class DispatchWait {
private init () { }
    

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

使用DispatchWait的例子:

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


DispatchWait.for(.seconds(3))


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

不幸的是,我不能说这个延迟的准确性是多少,以及wait(timeout:)方法允许程序在指定的延迟之后继续执行的概率是多少。

此外,此解决方案允许您延迟当前队列中的代码,而不必在单独的闭包中执行它。

使用DispatchQueue.asyncAfter方法,可以在给定时间后执行代码。例如,1秒后在主线程上执行...,如下所示:

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

使用我方便的Delay包装器结构,你可以用更奇妙的方式来执行它:

struct Delay {


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


}

用法:

Delay(0.4) { ... }

斯威夫特5 & lt;

使用Task.sleep将不会阻塞手头任务以外的任何代码,而且非常简单。

//Delay task by 4 seconds:


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

我认为最简单和最新的4秒计时器方法是:

Task {
// Do something


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


}

它使用Swift 5.5的新并发。

Apple's concurrent

这是添加延迟的一种更简单的方式,不会影响线程执行。

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