gcd-in-swift
[译]深入研究GCD在Swift中的用法
原作者: John Sundell
原文地址: https://www.swiftbysundell.com/posts/a-deep-dive-into-grand-central-dispatch-in-swift
翻译: 何东彬
校对: 韩晔
Grand Central Dispatch(或者简称GCD)是Swift开发者经常使用的基本技术之一.其最为人知的亮点是能够在不同的并发队列上分发任务,相信你很有可能已经用它来编写过如下代码:
1 | DispatchQueue.main.async { |
但是如果你再深入研究一下,会发现GCD还有一套你并不知道的并且非常强大的API和功能.这周,让我们将目光越过async {}
,进一步去发现它如何为其他大量的Foundation API提供更简单的(更灵活更具Swifty特色的)方法.
使用DispatchWorkItem进行可取消任务的延迟工作
关于GCD最常见的误解是”一旦你安排了一个任务就无法取消,需要操作Operation
的API来取消任务”.这一看法在以前是对的,但在iOS 8和macOS 10.10中DispatchWorkItem
被引入之后情况就不一样了,因为它提供了针对解决这一问题的功能,使得使用API非常简便.
假设我们的界面有个搜索框,当用户输入一个字符,我们通过调用后台接口来进行搜索.用户可以很快地打字,在这种情况下我们不能因为有一个字符输入就请求网络(这样会浪费大量的数据和服务器容量),实际上我们想要”防反跳”这些事件,如果用户0.25秒内没有进行输入就执行一次请求.
这种情况下,DispatchWorkItem
就会起作用.通过将我们的请求代码封装在work item
中,我们就可以在一个新的请求进入时非常容易地取消它,就像这样:
1 | class SearchViewController: UIViewController, UISearchBarDelegate { |
如上所述,由于尾随闭包和GCD在Swift中的导入程度,在Swift中使用DispatchWorkItem
要远比使用Timer或者Operation更简单友好.你不需要使用@objc
这种标记语法,或者#selector
,仅仅使用闭包就可以完成这一过程.
使用DispatchGroup进行分组或者链接任务
有时候要走通逻辑,你需要执行一个组任务.比如你在创建模型之前需要先从一组数据源中加载数据.通过使用DispatchGroup
,你不必一直关注数据源动向就可以轻松地进行同步工作.
使用dispatch groups
还有一个很大的优势,那就是你的任务可以并发运行在单独的队列中.这样你就可以有一个更简洁的开端,而且在有需要的情况下不必重写任何任务就能轻松地添加并发任务.你所需要做的就是在dispatch group
中平衡调用enter()
和leave()
来同步你的任务
译者注:enter()和leave()函数是成对调用的,不可单独出现.
让我们来看一个例子,从本地数据库,iCloud,后台加载笔记数据,然后把获取的全部数据合并到一个NoteCollection
中:
1 | // First, we create a group to synchronize our tasks |
上面的代码可以运行,但是重复率太高.如果Element
符合数据源,我们就可以给Array
写个扩展来重构下代码:
1 | extension Array where Element: DataSource { |
通过上面的扩展,我们可以把代码精简成这样:
1 | let dataSources = [localDataSource, iCloudDataSource, backendDataSource] |
使用DispatchSemaphore等待异步任务
DispatchGroup
既提供了一个非常简洁友好的方法来同步一组异步操作,同时这些操作又能保持异步状态,而DispatchSemaphore
则提供了一种同步等待一组异步任务的方法.这对于一个没有应用运行循环的命令行工具或者脚本来说非常有用,使用这种方法会在全局上下文中同步地执行,一直等到任务完成.
像DispatchGroup
一样,信号量的API是非常简单的,你只需要调用wait()
或者signal()
来增加或者减少内部计数器.在调用signal()
之前调用wait()
将会阻塞当前线程,直到接收到信号为止.
在我们之前的Array
的extension
上重写load
方法,它会同步地返回一个NoteCollection
或者抛出一个错误.我们可以重用之前基于DispatchGroup
的代码,只需要用信号量来简单地协调该任务即可.
1 | extension Array where Element: DataSource { |
在数组中使用上面的新方法,我们就可以通过脚本或者命令行工具同步地加载笔记数据,就像下面这样:
1 | let dataSources = [localDataSource, iCloudDataSource, backendDataSource] |
使用DispatchSource监控一个文件的变化
我想分享的最后一个”鲜为人知”的GCD特性是它如何让你在文件系统中监控一个文件的改变.像DispatchSemaphore
一样,如果你想自动对用户正在编辑的文件作出反应,DispatchSource
在脚本或者命令行工具中会起很大的作用.这个方法会帮助你轻松构建具有”live editing”特性的开发者工具.
基于你正在观察的对象,调度来源以不同的变量体现.这种情况下,我们将使用DispatchSourceFileSystemObject
来监控文件系统中的一些事件.
你可以使用fileDescriptor
和DispatchQueue
创建一个调度源来执行监控.下面有一个简单的FileObserver
类实现例子,在这个示例中,每次对改给定文件进行修改时会运行闭包(可以使用File来获取文件索引)
1 | class FileObserver { |
我们可以像下面这样使用FileObserver
:
1 | let observer = try FileObserver(file: file) |
小结
Grand Central Dispatch是一个非常强大的框架,它远比看起来厉害多了.希望这篇文章可以带给你灵感,从而让你可以更好的使用它.下次在需要完成文章中提到的任务时可以试一下这些方法.在我看来,很多基于Timer
或者OperationQueue
的代码,还有一些第三方的框架,都没有直接使用GCD来的简单些.
gcd-in-swift