ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Swift 中创建延迟?

我想在某个时间点暂停我的应用程序。换句话说,我希望我的应用程序执行代码,但在某个时间点暂停 4 秒,然后继续执行其余代码。我怎样才能做到这一点?

我正在使用斯威夫特。


P
Palle

在大多数情况下,使用 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 {
    try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))
    // 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
}

周期性动作呢?
有可能将 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
n
nneonneo

如果从 UI 线程调用会锁定您的程序的睡眠,请考虑使用 NSTimer 或调度计时器,而不是睡眠。

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

do {
    sleep(4)
}

这使用了 UNIX 中的 sleep 函数。


sleep 在不导入 darwin 的情况下工作,不确定这是否是一个变化
如果您需要比 1 秒分辨率更精确的东西,usleep() 也可以使用。
usleep() 需要百万分之一秒,所以 usleep(1000000) 会休眠 1 秒
感谢您保持简短的“不要这样做”序言。
我使用 sleep() 来模拟长时间运行的后台进程。
F
Fangming

swift 3.0中不同方法的比较

1. 睡觉

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

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

https://i.stack.imgur.com/0zWFz.gif

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 不会冻结,您可以再次单击它。如果再次单击该按钮,则会设置另一个计时器并触发两次回调。

https://i.stack.imgur.com/qe8tL.gif

综上所述

这四种方法都不能单独使用。 sleep 将禁用用户交互,因此屏幕“冻结”(实际上并非如此)并导致糟糕的用户体验。其他三种方法不会冻结屏幕,但您可以多次触发它们,并且大多数情况下,您希望等到您收到回电后再允许用户再次拨打电话。

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


请注意,在 Swift 4 中,关闭了 objc 推理,您需要在 perform(...) 选项的回调函数前面添加 @objc。像这样:@objc func callback() {
N
N.W

在 Swift 4.2 和 Xcode 10.1 中

您总共有 4 种延迟方式。在这些选项中,最好在一段时间后调用或执行函数。 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)

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


J
Jeehut

我同意 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 自动转换的结果,之前需要类型转换,因为 Swift 2 中没有 DispatchTime。之前是 let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))
C
Cyril

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

C
Community

NS定时器

@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


V
Vakas

在 Swift 3.0 中尝试以下实现

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

用法

delayWithSeconds(1) {
   //Do something
}

B
Booharin

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

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

H
Hardik Thakkar

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

R
Robin Stewart

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

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


S
Sentry.co
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*/}可能更正确✌️
E
Ethan Wrightson

要创建一个简单的时间延迟,您可以导入 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 至关重要... :) :)
R
Robert Dresler

使用 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) { ... }

L
Legolas Wang

我相信做 4 秒计时器的最简单和最新的方法是:

Task { 
    // Do something

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

}

它使用 Swift 5.5 的新并发。

https://i.stack.imgur.com/h1F81.png


A
Asio Otus

作为之前提出的选项的替代解决方案,您可以使用基于 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:) 方法允许程序在指定延迟之后进一步执行的概率是多少。

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


J
Jorge CF

斯威夫特 5<

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

//Delay task by 4 seconds:

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

t
taha

这是最简单的

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

这不是 Foundation 的一部分,我认为它包含在“HandySwift”Cocoapod 中。您应该在回答中澄清这一点。
swift 有没有等待某事发生的事情?