如何在 Swift 中创建延迟?
Posted
技术标签:
【中文标题】如何在 Swift 中创建延迟?【英文标题】:How to create a delay in Swift? 【发布时间】:2015-02-15 12:45:31 【问题描述】:我想在某个时间点暂停我的应用程序。换句话说,我希望我的应用程序执行代码,然后在某个时间点暂停 4 秒,然后继续执行其余代码。我该怎么做?
我正在使用 Swift。
【问题讨论】:
***.com/a/24318861/716216 【参考方案1】:在大多数情况下,使用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
在异步上下文中 Swift ≥ 5.5:
func foo() async
await Task.sleep(UInt64(seconds * Double(NSEC_PER_SEC)))
// Put your code which should be executed with a delay here
斯威夫特
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
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
【讨论】:
周期性动作呢? 有可能将 gcd 用于this article from the apple developer documentation 中描述的定期任务,但我建议为此使用 NSTimer。 this answer 展示了如何快速做到这一点。 为 Xcode 8.2.1 更新了代码。以前.now() + .seconds(4)
给出错误:expression type is ambiguous without more context
如何暂停从队列中调用的函数?@Palle
您不再需要添加.now() + .seconds(5)
,只需添加.now() + 5
【参考方案2】:
考虑使用NSTimer
或调度计时器,而不是睡眠(如果从 UI 线程调用会锁定您的程序)。
但是,如果你真的需要当前线程的延迟:
do
sleep(4)
这使用了来自 UNIX 的 sleep
函数。
【讨论】:
sleep 可以在不导入 darwin 的情况下工作,不确定这是否是一个变化 usleep() 也可以,如果您需要比 1 秒更精确的分辨率。 usleep() 需要百万分之一秒,所以 usleep(1000000) 会休眠 1 秒 感谢您保持简短的“不要这样做”序言。 我使用 sleep() 来模拟长时间运行的后台进程。【参考方案3】: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
将禁用用户交互,因此屏幕“冻结”(实际上并非如此)并导致糟糕的用户体验。其他三种方法不会冻结屏幕,但您可以多次触发它们,而且大多数情况下,您希望等到您收到回电后再允许用户再次拨打电话。
因此,更好的设计是使用三种异步方法中的一种来屏蔽屏幕。当用户点击按钮时,用一些半透明视图覆盖整个屏幕,顶部有一个旋转的活动指示器,告诉用户正在处理按钮点击。然后移除回调函数中的view和indicator,告诉用户该action被正确处理等等。
【讨论】:
请注意,在 Swift 4 中,关闭了 objc 推理,您需要在perform(...)
选项的回调函数前面添加 @objc
。像这样:@objc func callback()
【参考方案4】:
在 Swift 4.2 和 Xcode 10.1 中
您总共有 4 种方法可以延迟。在这些选项1中,最好在一段时间后调用或执行一个函数。 sleep() 是最少使用的情况。
选项 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。
【讨论】:
【参考方案5】:我同意 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。您可以通过SwiftPM将它添加到您的项目中,然后像上面的示例一样使用它:
import HandySwift
delay(by: .seconds(1.5))
// delayed code
【讨论】:
这看起来很有帮助。你介意用 swift 3.0 版本更新它吗?谢谢 我开始将 HandySwift 迁移到 Swift 3 并计划下周发布新版本。然后我也要更新上面的脚本。请继续关注。 HandySwift 到 Swift 3 的迁移现已完成,并在 1.3.0 版中发布。我还刚刚更新了上面的代码以使用 Swift 3! :) 你为什么要乘以 NSEC_PER_SEC 然后再除以它?这不是多余的吗? (例如 Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) 你不能只写 'DispatchTime.now() + Double(seconds) 吗? 谢谢@MarqueIV,你当然是对的。我刚刚更新了代码。这只是 Xcode 8 自动转换的结果,并且之前需要类型转换,因为DispatchTime
在 Swift 2 中不可用。之前是 let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
。【参考方案6】:
您也可以使用 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")
【讨论】:
【参考方案7】: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's answer)是另一个有效选项。但是,它是hard to cancel。使用NSTimer
,要在延迟事件发生之前取消它,您需要做的就是调用
timer.invalidate()
不推荐使用sleep
,尤其是在主线程上,因为它会停止在线程上完成的所有工作。
请参阅 here 以获得更完整的答案。
【讨论】:
【参考方案8】:在 Swift 3.0 中尝试以下实现
func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ())
DispatchQueue.main.asyncAfter(deadline: .now() + seconds)
completion()
用法
delayWithSeconds(1)
//Do something
【讨论】:
【参考方案9】:如果需要设置小于一秒的延迟,则无需设置 .seconds 参数。我希望这对某人有用。
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute:
// your code hear
)
【讨论】:
【参考方案10】:您可以轻松创建扩展以使用延迟功能(语法: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
)
【讨论】:
【参考方案11】:如果您的代码已经在后台线程中运行,请使用 this method in 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)
(当您的代码在主线程上运行时,请参阅其他答案以获得建议。)
【讨论】:
【参考方案12】: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
【讨论】:
做DispatchQueue.main.asyncAfter(deadline: .now() + 4) /*Do stuff*/
可能更正确✌️【参考方案13】:
要创建一个简单的时间延迟,您可以导入 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 秒然后打印。一切都按预期工作。
【讨论】:
最后是一个很棒的简单答案。谢谢你。import Dawrin
至关重要... :) :)【参考方案14】:
作为之前提出的选项的替代解决方案,您可以使用基于 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:)
方法允许程序在指定延迟之后进一步执行的概率是多少。
此外,此解决方案允许您延迟当前队列中的代码,而无需在单独的闭包中执行它。
【讨论】:
【参考方案15】:使用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) ...
【讨论】:
【参考方案16】:这是最简单的
delay(0.3, closure:
// put her any code you want to fire it with delay
button.removeFromSuperview()
)
【讨论】:
这不是 Foundation 的一部分,我认为它包含在“HandySwift”Cocoapod 中。您应该在回答中澄清这一点。 swift 有没有等待某事发生的东西?以上是关于如何在 Swift 中创建延迟?的主要内容,如果未能解决你的问题,请参考以下文章