dispatch_after - 在Swift中使用GCD?

584

我已经阅读了苹果公司的iBook,但没有找到它的任何定义:

有人能解释一下dispatch_after的结构吗?

dispatch_after(<#when: dispatch_time_t#>, <#queue: dispatch_queue_t?#>, <#block: dispatch_block_t?#>)

1
苹果在2018年撤下了这本书。我能找到的最新存档是2017年12月。现在,旧链接到iBook只会重定向到https://developer.apple.com/documentation/swift。 - Cœur
26个回答

1114
我经常使用 dispatch_after,因此编写了一个顶级实用函数来使语法更简单:
func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

现在您可以这样说:

delay(0.4) {
    // do stuff
}

哇,一种可以提高编程语言的语言。有什么比这更好的吗?


针对 Swift 3、Xcode 8 Seed 6 的更新

现在他们改进了调用语法,似乎几乎不值得费事了:

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

2
我只是需要一个延迟计算的快捷方式,最终得到了这个:func delayInSec(delay: Double) -> dispatch_time_t { return dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))) } - Aviel Gross
4
如果闭包中只有一行代码,需要添加另一行代码(例如 return) 。 - matt
2
@GastonM 不相关。传递函数本身没有内存管理问题。 - matt
7
“一种可以提高语言的语言”。我不明白定义全局函数如何能够提高语言,也不明白为什么在C中不能执行这个操作。或许你可以通过重载运算符 1.0 ~~ { code...} 来实现。 - Yerk
8
不质疑你回答的正确性,但是“我经常使用dispatch_after”这句话是否暗示着代码有问题?最好的解决方法不是不要提供便利函数吗? - Nikolai Ruhe
显示剩余20条评论

767

结构更清晰的概念:

dispatch_after(when: dispatch_time_t, queue: dispatch_queue_t, block: dispatch_block_t?)

dispatch_time_t 是一个 UInt64。实际上, dispatch_queue_t 被类型别名为一个 NSObject,但你应该使用常见的 GCD 方法来获取队列。块是一个 Swift 闭包。具体而言,dispatch_block_t 定义为 () -> Void,它等同于 () -> ()

示例用法:

let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
    print("test")
}

编辑:

我建议使用@matt的非常好的delay函数.

编辑2:

在Swift 3中,将会有新的GCD包装器。请参见:https://github.com/apple/swift-evolution/blob/master/proposals/0088-libdispatch-for-swift3.md

在Swift 3中,原始示例将编写如下:

let deadlineTime = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
    print("test")
}

请注意,您可以将 deadlineTime 声明写成 DispatchTime.now() + 1.0 ,并获得相同的结果,因为 + 运算符被重载如下(- 同理):

  • func +(time: DispatchTime, seconds: Double) -> DispatchTime
  • func +(time: DispatchWalltime, interval: DispatchTimeInterval) -> DispatchWalltime

这意味着,如果您没有使用 DispatchTimeInterval 枚举并只写一个数字,那么就假定您使用的是秒。


18
提示:由于块是函数的最后一个参数,您可以使用Swift的“尾随闭包”语法实现更好的可读性:dispatch_after(1, dispatch_get_main_queue()) { println("test") } - Bill
8
我认为在dispatch_after(1, ...)中使用数字1可能会引起很多混淆。人们会认为它是秒数,实际上它是纳秒。我建议查看@brindy的答案以了解如何正确创建这个数字。 - Hlung
4
请将1更改为 dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))),因为它容易引起混淆。人们可能会认为在Swift中不需要创建dispatch_time_t - OemerA
4
Swift 3版本似乎无法运行。在这一行代码let delayTime = DispatchTime.now() + .seconds(1.0)上,它会抱怨二元运算符'+'不能将类型为DispatchTime和'_'的操作数应用于运算 - Andy Ibanez
10
改为 DispatchTime.now() + 1.0 的写法似乎是让它正常工作的唯一方法(无需加上 .seconds)。 - Andy Ibanez
显示剩余3条评论

157

Swift 3+

在Swift 3+中,这非常简单而优雅:

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

较早的答案:

对于 Cezary 的答案进行补充,如果想要在4.5秒后执行,我必须执行以下操作。

let delay = 4.5 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), block)

编辑:我发现我的原始代码略有错误。如果您不将NSEC_PER_SEC强制转换为Double,则隐式类型会导致编译错误。

如果有人能提出更优化的解决方案,我很乐意听取建议。


我使用dispatch_get_main_queue()替代了已弃用的API dispatch_get_current_queue(),因此编译器出现错误。 - David L
@DavidL - 谢谢,dispatch_get_main_queue() 绝对是你应该使用的。我会更新的。 - brindy
我在使用Swift 3的Playground尝试过了,但它并没有起作用。 - μολὼν.λαβέ
嗯,好吧,我让它运行了几个小时,但仍然没有输出任何内容。这是我使用的代码:"import Dispatch import Darwin import CoreGraphics 'DispatchQueue.main.asyncAfter(deadline: .now() + 4.5) { print(" got here ") } " - μολὼν.λαβέ
@GAlexander 啊 - 你必须明确告诉 Playground 允许无限执行。请参见 https://dev59.com/c2Ag5IYBdhLWcg3wBXAR#33536290。 - brindy
显示剩余2条评论

84

马特的语法非常好,如果您需要使块无效,您可能想要使用这个:

typealias dispatch_cancelable_closure = (cancel : Bool) -> Void

func delay(time:NSTimeInterval, closure:()->Void) ->  dispatch_cancelable_closure? {

    func dispatch_later(clsr:()->Void) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(time * Double(NSEC_PER_SEC))
            ),
            dispatch_get_main_queue(), clsr)
    }

    var closure:dispatch_block_t? = closure
    var cancelableClosure:dispatch_cancelable_closure?

    let delayedClosure:dispatch_cancelable_closure = { cancel in
        if closure != nil {
            if (cancel == false) {
                dispatch_async(dispatch_get_main_queue(), closure!);
            }
        }
        closure = nil
        cancelableClosure = nil
    }

    cancelableClosure = delayedClosure

    dispatch_later {
        if let delayedClosure = cancelableClosure {
            delayedClosure(cancel: false)
        }
    }

    return cancelableClosure;
}

func cancel_delay(closure:dispatch_cancelable_closure?) {

    if closure != nil {
        closure!(cancel: true)
    }
}

使用方法如下

let retVal = delay(2.0) {
    println("Later")
}
delay(1.0) {
    cancel_delay(retVal)
}

credits

上面的链接似乎已失效。 来自Github的原始Objc代码


1
performSelector:afterDelay 方法的一个性能特点是可以取消它。只有这种解决方案才能解决该问题。谢谢。 - HotJard
请注意,在Swift 2中,performSelector:afterDelay:现在可用,因此您可以取消它。 - matt
@HotJard 当然,这比根本没有好。我在这方面看不出任何问题。然而,就像这个答案一样,我已经通过编写基于GCD的可取消计时器(使用dispatch_source_t,因为您可以取消它)来补偿其损失。 - matt
我该如何使得我可以从另一个方法(比如didSelectRowAtIndexPath)中调用delay(1.0) { cancel_delay(retVal) }? - MikeG
2
非常感谢,我一直使用这个到Swift 2.3。现在Swift 3.0编译器报错了,如果您更新您的答案会很好! - nontomatic
显示剩余2条评论

32

Swift 3.0、Swift 4.0 和 Swift 5.0 中最简单的解决方案

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

使用方法

delayWithSeconds(1) {
   //Do something
}

24

苹果公司为 Objective-C 提供了一个 dispatch_after 代码片段

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    <#code to be executed after a specified delay#>
});

这里是相同的代码片段,移植到 Swift 3:

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + <#delayInSeconds#>) {
  <#code to be executed after a specified delay#>
}

15

另一种方法是扩展Double,如下所示:

extension Double {
   var dispatchTime: dispatch_time_t {
       get {
           return dispatch_time(DISPATCH_TIME_NOW,Int64(self * Double(NSEC_PER_SEC)))
       }
   }
}

然后你可以像这样使用它:

dispatch_after(Double(2.0).dispatchTime, dispatch_get_main_queue(), { () -> Void in
            self.dismissViewControllerAnimated(true, completion: nil)
    })

我喜欢马特的延迟函数,但出于偏好,我宁愿限制传递闭包。


9

In Swift 3.0

Dispatch queues

  DispatchQueue(label: "test").async {
        //long running Background Task
        for obj in 0...1000 {
            print("async \(obj)")
        }

        // UI update in main queue
        DispatchQueue.main.async(execute: { 
            print("UI update on main queue")
        })

    }

    DispatchQueue(label: "m").sync {
        //long running Background Task
        for obj in 0...1000 {
            print("sync \(obj)")
        }

        // UI update in main queue
        DispatchQueue.main.sync(execute: {
            print("UI update on main queue")
        })
    }

5秒后分派任务

    DispatchQueue.main.after(when: DispatchTime.now() + 5) {
        print("Dispatch after 5 sec")
    }

5

1) 将此方法作为UIViewController扩展的一部分添加。

extension UIViewController{
func runAfterDelay(delay: NSTimeInterval, block: dispatch_block_t) {
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
        dispatch_after(time, dispatch_get_main_queue(), block)
    }
}

在视图控制器上调用此方法:

    self.runAfterDelay(5.0, block: {
     //Add code to this block
        print("run After Delay Success")
    })

2)
第二点:
performSelector("yourMethod Name", withObject: nil, afterDelay: 1)

3)
override func viewWillAppear(animated: Bool) {

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue(), { () -> () in
    //Code Here
})

//紧凑表单

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue()) {
    //Code here
 }
}

5
尽管这不是原始问题,但某些与NSTimer相关的问题已被标记为此问题的重复项,因此在此包括一个NSTimer答案。

NSTimer vs dispatch_after

  • NSTimer更高层次,而dispatch_after更低级。
  • NSTimer更容易取消。取消dispatch_after需要编写更多代码

使用NSTimer延迟任务

创建一个NSTimer实例。

var timer = NSTimer()

使用所需的延迟启动计时器。

// invalidate the timer if there is any chance that it could have been called before
timer.invalidate()
// delay of 2 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 

在延迟后添加一个函数(使用您在上面的selector参数中使用的任何名称)。
func delayedAction() {
    print("Delayed action has now started."
}

笔记

  • If you need to cancel the action before it happens, simply call timer.invalidate().
  • For a repeated action use repeats: true.
  • If you have a one time event with no need to cancel then there is no need to create the timer instance variable. The following will suffice:

    NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 
    
  • See my fuller answer here.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接