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)