如何在Swift 3、4和5中编写dispatch_after GCD?

8ehkhllq  于 2023-03-07  发布在  Swift
关注(0)|答案(9)|浏览(195)

在Swift 2中,我可以使用dispatch_after来延迟使用grand central dispatch的操作:

var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))) 
dispatch_after(dispatchTime, dispatch_get_main_queue(), { 
    // your function here 
})

但这似乎不再编译自斯威夫特3。什么是首选的方式来写这在现代斯威夫特?

piwo6bdm

piwo6bdm1#

其语法简单明了:

// to run something in 0.1 seconds

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    // your code here
}

请注意,上述将seconds添加为Double的语法似乎会引起混淆(特别是因为我们习惯于添加nsec)。“将秒添加为Double“语法之所以有效,是因为deadline是一个DispatchTime,并且在幕后,有一个+运算符,它将取一个Double,并在DispatchTime上加上那么多秒:

public func +(time: DispatchTime, seconds: Double) -> DispatchTime

但是,如果你真的想给DispatchTime加上整数毫秒、微秒或纳秒,你也可以给DispatchTime加上一个DispatchTimeInterval

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
    // 500 msec, i.e. 0.5 seconds
    …
}

DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(1_000_000)) {
    // 1m microseconds, i.e. 1 second
    …
}

DispatchQueue.main.asyncAfter(deadline: .now() + .nanoseconds(1_500_000_000)) {
    // 1.5b nanoseconds, i.e. 1.5 seconds
    …
}

由于DispatchTime类中+运算符的这个单独的重载方法,所有这些都可以无缝地工作。

public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime

有人问如何取消一个已分派的任务。要做到这一点,请使用DispatchWorkItem。例如,这将启动一个将在五秒后触发的任务,或者如果视图控制器被解除并重新分配,则其deinit将取消该任务:

class ViewController: UIViewController {

    private var item: DispatchWorkItem?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        item = DispatchWorkItem { [weak self] in
            self?.doSomething()
            self?.item = nil
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: item!)
    }

    deinit {
        item?.cancel()
    }
    
    func doSomething() { … }
}

请注意DispatchWorkItem[weak self]捕获列表的使用。这对于避免强引用循环至关重要。还要注意,这不会执行抢先取消,而只是在任务尚未启动时停止任务启动。但如果任务在遇到cancel()调用时已经启动,块将完成其执行(除非您手动检查块内的isCancelled)。

快速并发

虽然最初的问题是关于旧的GCD dispatch_after与新的asyncAfter API,但这引发了如何在新的Swift并发及其async-await中实现相同行为的问题。从iOS 16和macOS 13开始,我们倾向于Task.sleep(for:)

try await Task.sleep(for: .seconds(2))        // 2 seconds
…

或者

try await Task.sleep(for: .milliseconds(200)) // 0.2 seconds
…

或者,如果我们需要重新支持iOS 13和macOS 10.15,我们将使用Task.sleep(nanoseconds:)
要支持取消,请保存Task

class ViewController: UIViewController {

    private var task: Task<Void, Error>?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        task = Task {
            try await Task.sleep(for: .seconds(5))
            await doSomething()
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        task?.cancel()
    }

    func doSomething() async { … }
}

或者在SwiftUI中,我们可以使用.task {…}视图修饰符,它“将在视图消失后的某个时间点自动取消任务,直到动作完成。”我们甚至不需要保存Task以便以后手动取消。
我们应该认识到,在Swift并发之前,调用任何sleep函数都是一个反模式,我们会刻意避免,因为它会阻塞当前线程。但即使在后台线程中使用也会有问题,因为GCD工作线程池非常有限。阻塞当前线程,因此可以安全地从任何参与者(包括主要参与者)使用。

amrnrhlw

amrnrhlw2#

    • 雨燕4:**
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
   // Code
}

对于时间.seconds(Int),也可以使用.microseconds(Int).nanoseconds(Int)

tpxzln5u

tpxzln5u3#

如果你只想要延迟函数

雨燕4和5

func delay(interval: TimeInterval, closure: @escaping () -> Void) {
     DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
          closure()
     }
}

您可以像这样使用它:

delay(interval: 1) { 
    print("Hi!")
}
llmtgqce

llmtgqce4#

Swift 3发布后,还必须添加@escaping

func delay(_ delay: Double, closure: @escaping () -> ()) {
  DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
    closure()
  }
}
ioekq8ef

ioekq8ef5#

一个有点不同的味道的接受答案。

    • 雨燕4**
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 + .milliseconds(500) + 
.microseconds(500) + .nanoseconds(1000)) {
                print("Delayed by 0.1 second + 500 milliseconds + 500 microseconds + 
                      1000 nanoseconds)")
 }
1tu0hz3e

1tu0hz3e6#

    • 雨燕4**

您可以在DispatchQueue上创建一个扩展并添加函数delay,该函数在内部使用DispatchQueue asyncAfter函数

extension DispatchQueue {
   static func delay(_ delay: DispatchTimeInterval, closure: @escaping () -> ()) {
      DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure)
   }
}

并使用

DispatchQueue.delay(.milliseconds(10)) {
   print("task to be done")
}
vyswwuz2

vyswwuz27#

调用DispatchQueue.main.after(when: DispatchTime, execute: () -> Void)
我强烈推荐使用Xcode工具转换到Swift3(编辑〉转换〉到当前SwiftSyntax)。

lxkprmvk

lxkprmvk8#

Swift 5及以上

DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
   // code to execute                 
})
aemubtdh

aemubtdh9#

没有一个答案提到运行在非主线程上,所以加上我的两分钱。
主队列上(主线程)

let mainQueue = DispatchQueue.main
let deadline = DispatchTime.now() + .seconds(10)
mainQueue.asyncAfter(deadline: deadline) {
    // ...
}

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(10)) { 
    // ...
}

全局队列上(非主线程,基于指定的QOS)。

let backgroundQueue = DispatchQueue.global()
let deadline = DispatchTime.now() + .milliseconds(100)
backgroundQueue.asyncAfter(deadline: deadline, qos: .background) { 
    // ...
}

DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + .milliseconds(100), qos: .background) {
    // ...
}

相关问题