当前位置: 首页 > 工具软件 > Swift-T > 使用案例 >

Swift - GCD 和延时调用

苏俊友
2023-12-01

GCD 是一种非常方便的使用多线程的方式。通过使用GCD,我们可以在确保语法尽量简单的前提下进行灵活的多线程编程。在“复杂必死”的多线程编程中,保持简单就是避免错误的金科玉律。好消息是在Swift 中是可以无缝使用GCD 的API 的,而且得益于闭包特性的加入,使用起来比之前在Objective-C 中更加简单方便。在这里我不打算花费很多时间介绍GCD 的语法和要素,否则就可以专门为GCD 写上一节了。下面给出了一个日常生活中最常使用的例子(说这个例子能覆盖到日常GCD 使用的50% 以上也不为过),来展示一下Swift 里的GCD 调用会是什么样子:

let workingQueue = DispatchQueue.init(label: "my_queue")
workingQueue.async {
    // 在 workingQueue 中异步进行
    print("努力学习")
    Thread.sleep(forTimeInterval: 2) // 模拟两秒的执行时间
            
    DispatchQueue.main.async {
        // 返回到主线程更新UI
        print("结束学习,更新UI")
    }
}

因为UIKit 是只能在主线程工作的,如果主线程的工作过于繁重的话,就会导致App 出现“卡死”的现象:UI 不能更新,用户输入无法响应等等,这是非常糟糕的用户体验。为了避免这种情况的出现,对于繁重的(如图像加滤镜等)或者很长时间才能完成的(如从网络下载图片)任务,我们应该把它们放到后台线程进行,这样在用户看来UI 还是可以交互的,也不会出现卡顿。在工作进行完成后,我们需要更新UI 的话,必须回到主线程进行(要牢记和UI 相关的工作都需要在主线程执行,否则可能发生不可预知的错误)。

在日常的开发工作中,我们经常会遇到这样的需求:在若干秒后执行某个方法。比如切换界面 2秒后开始播一个动画,或者提示框出现 3秒后自动消失等等。以前在Objective-C 中,我们可以使用一个NSObject 的实例方法 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay 来指定在一定时间后执行某个Selector。不过如果你现在新建一个Swift 的项目,并且试图使用这个方法(或者这个方法的其他一切变形)的话,会发现这个方法已经不见了!

发生什么了?因为我们强调过很多次,Swift 的一大追求就是安全两个字,但是原来的performSelector: 这套东西在ARC 下并不安全的。因为ARC 为了确保参数在方法运行期间的存在,会将输入参数在方法开始时先进行retain,然后在最后release。而对于performSelector: 这个方法我们并没有机会为被调用的方法指定参数,于是被调用的selector的输入有可能指向未知垃圾内存地址,然后… 更要命的是这种崩溃不能每次重现

但无论如何都想继续做延时调用的话,我们该怎么办呢?最容易想到的是使用Timer 来创建一个若干秒后调用一次的计时器。但是这么做我们需要创建新的对象,和一个本来不相干的Timer 类扯上关系,同时也会用到Objective-C 的运行时特性去查找方法等,总觉着有点笨。其实GCD 里有一个很好用的延时调用,我们可以加以利用写出很漂亮的方法来,那就是after。最简单的使用方法看起来是这样的:

let time: TimeInterval = 2.0
let delay = DispatchTime.init(uptimeNanoseconds: DispatchTime.now() + time)
DispatchQueue.main.asyncAfter(deadline: delay) {
    print("2秒后输出")
}

代码非常简单,并没有什么值得详细说明的。只是每次写这么多的话也挺累的,在这里我们可以稍微将它封装的好用一些,最好再加上取消的功能。

import Foundation

typealias Task = (Bool) -> ()

func delay(_ time: TimeInterval, task: @escaping () -> Void) -> Task? {
    func dispatchLater(block: @escaping () -> Void) {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time, execute: block)
    }
    
    var closure: Dispatch.os_block_t? = task
    var result: Task?
    
    let delayedClosure: Task = { cancel in
        if let internalClosure = closure {
            if cancel == false {
                DispatchQueue.main.async(execute: internalClosure)
            }
        }
        closure = nil
        result = nil
    }
    
    result = delayedClosure
    
    dispatchLater {
        if let delayedClosure = result {
            delayedClosure(false)
        }
    }
    
    return result
}

func cancel(_ task: Task?) {
    task?(true)
}

使用的时候就很简单了,我们想在2秒以后干点儿什么的话,就这样做:

delay(2) {
    print("2秒后输出")
}

想要取消的话,我们可以先保留一个对Task 的引用,然后调用cancel:

let task = delay(5) {
    print("5秒后输出")
}

// 立即取消
cancel(task)
 类似资料: