深入了解斯威夫特的Grand Central Dispatch

Grand Central Dispatch(简称GCD)是大多数Swift开发人员无数次使用的基本技术之一。它主要用于能够在不同的并发队列上调度工作,并且经常用于编写如下代码:

DispatchQueue.main.async {
    // Run async code on the main queue
}
复制代码

但事实证明,如果我们深入一点,GCD还有一套非常强大的API和功能,并不是每个人都知道的。本周,让我们先async {}看看GCD可能非常有用的一些情况,以及它如何为许多其他 - 更常见的 - 基础API提供更简单(和更“Swifty”)的选项。

使用延迟延迟可取消的任务 DispatchWorkItem

关于GCD的一个常见误解是*“一旦你安排了一项无法取消的任务,就需要使用OperationAPI”*。虽然过去曾经如此,但DispatchWorkItem引入了iOS 8和macOS 10.10 ,它在一个非常易于使用的API中提供了这一功能。

假设我们的UI有一个搜索栏,当用户键入一个字符时,我们通过调用我们的后端来执行搜索。由于用户可以非常快速地打字,我们不想立即启动我们的网络请求(这可能会浪费大量数据和服务器容量),而是我们将*“去抖”*这些事件并仅执行请求一旦用户没有输入0.25秒。

这就是它的DispatchWorkItem用武之地。通过将我们的请求代码封装在一个工作项中,只要它被一个新的替换,我们就可以很容易地取消它,如下所示:

class SearchViewController: UIViewController, UISearchBarDelegate {
    // We keep track of the pending work item as a property
    private var pendingRequestWorkItem: DispatchWorkItem?

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        // Cancel the currently pending item
        pendingRequestWorkItem?.cancel()

        // Wrap our request in a work item
        let requestWorkItem = DispatchWorkItem { [weak self] in
            self?.resultsLoader.loadResults(forQuery: searchText)
        }

        // Save the new work item and execute it after 250 ms
        pendingRequestWorkItem = requestWorkItem
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250),
                                      execute: requestWorkItem)
    }
}
复制代码

正如我们上面所看到的,使用DispatchWorkItemSwift实际上比使用a Timer或者更简单更好Operation,这要归功于尾随闭包语法以及GCD导入Swift的程度。我们不需要@objc标记方法或#selector- 它们都可以用闭包来完成。

使用分组和链接任务 DispatchGroup

有时我们需要执行一组操作才能继续使用逻辑。例如,假设我们需要在创建模型之前从一组数据源加载数据。我们不必自己跟踪所有数据源,而是可以轻松地将工作与工作同步 DispatchGroup

使用调度组还为我们提供了一个很大的优势,即我们的任务可以在不同的队列中同时运行。这使我们能够从简单开始,然后在需要时轻松添加并发,而无需重写任何任务。我们所要做的就是做出平衡的电话enter()leave()一个调度组把它同步我们的任务。

让我们看一个例子,我们从本地存储,iCloud Drive和后端系统加载笔记,然后将所有结果合并到NoteCollection

// First, we create a group to synchronize our tasks
let group = DispatchGroup()

// NoteCollection is a thread-safe collection class for storing notes
let collection = NoteCollection()

// The 'enter' method increments the group's task count…
group.enter()
localDataSource.load { notes in
    collection.add(notes)
    // …while the 'leave' methods decrements it
    group.leave()
}

group.enter()
iCloudDataSource.load { notes in
    collection.add(notes)
    group.leave()
}

group.enter()
backendDataSource.load { notes in
    collection.add(notes)
    group.leave()
}

// This closure will be called when the group's task count reaches 0
group.notify(queue: .main) { [weak self] in
    self?.render(collection)
}
复制代码

上面的代码有效,但它有很多重复。让我们将其重构为扩展Array,使用DataSource协议作为其Element类型的相同类型约束:

extension Array where Element == DataSource {
    func load(completionHandler: @escaping (NoteCollection) -> Void) {
        let group = DispatchGroup()
        let collection = NoteCollection()

        // De-duplicate the synchronization code by using a loop
        for dataSource in self {
            group.enter()
            dataSource.load { notes in
                collection.add(notes)
                group.leave()
            }
        }

        group.notify(queue: .main) {
            completionHandler(collection)
        }
    }
}
复制代码

通过上面的扩展,我们现在可以将以前的代码减少到:

let dataSources: [DataSource] = [
    localDataSource,
    iCloudDataSource,
    backendDataSource
]

dataSources.load { [weak self] collection in
    self?.render(collection)
}
复制代码

非常好,紧凑!?

等待异步任务 DispatchSemaphore

虽然DispatchGroup提供了一种同步一组异步操作同时仍保持异步的简单方法,但DispatchSemaphore它提供了一种同步等待一组异步任务的方法。这在命令行工具或脚本中非常有用,我们没有应用程序运行循环,而只是在全局上下文中同步执行,直到完成为止。

就像DispatchGroup,信号量API非常简单,因为我们只通过调用wait()或递增或递减内部计数器signal()wait()在a之前调用signal()将*阻止当前队列,*直到收到信号。

让我们在Array之前的扩展中创建另一个重载,它会NoteCollection同步返回,否则会抛出错误。我们将重用DispatchGroup以前的基于代码的代码,但只需使用信号量协调该任务。

extension Array where Element == DataSource {
    func load() throws -> NoteCollection {
        let semaphore = DispatchSemaphore(value: 0)
        var loadedCollection: NoteCollection?

        // We create a new queue to do our work on, since calling wait() on
        // the semaphore will cause it to block the current queue
        let loadingQueue = DispatchQueue.global()

        loadingQueue.async {
            // We extend 'load' to perform its work on a specific queue
            self.load(onQueue: loadingQueue) { collection in
                loadedCollection = collection

                // Once we're done, we signal the semaphore to unblock its queue
                semaphore.signal()
            }
        }

        // Wait with a timeout of 5 seconds
        semaphore.wait(timeout: .now() + 5)

        guard let collection = loadedCollection else {
            throw NoteLoadingError.timedOut
        }

        return collection
    }
}
复制代码

使用上面的新方法Array,我们现在可以在脚本或命令行工具中同步加载注释,如下所示:

let dataSources: [DataSource] = [
    localDataSource,
    iCloudDataSource,
    backendDataSource
]

do {
    let collection = try dataSources.load()
    output(collection)
} catch {
    output(error)
}
复制代码

观察文件中的更改 DispatchSource

我想提出的最后一个*“鲜为人知”的GCD功能是它如何提供一种方法来观察文件系统上文件的变化。比如DispatchSemaphore,如果我们想要自动对用户正在编辑的文件做出反应,这在脚本或命令行工具中非常有用。这使我们能够轻松构建具有“实时编辑”*功能的开发人员工具。

根据我们想要观察的内容,调度源有几种不同的变体。在这种情况下,我们将使用DispatchSourceFileSystemObject,它允许我们观察文件系统中的事件。

让我们看一个简单的示例实现FileObserver,它允许我们在每次更改给定文件时附加一个闭包。它通过使用a fileDescriptor和a 创建调度源DispatchQueue来执行观察,并使用Files来引用要观察的文件:

class FileObserver {
    private let file: File
    private let queue: DispatchQueue
    private var source: DispatchSourceFileSystemObject?

    init(file: File) {
        self.file = file
        self.queue = DispatchQueue(label: "com.myapp.fileObserving")
    }

    func start(closure: @escaping () -> Void) {
        // We can only convert an NSString into a file system representation
        let path = (file.path as NSString)
        let fileSystemRepresentation = path.fileSystemRepresentation

        // Obtain a descriptor from the file system
        let fileDescriptor = open(fileSystemRepresentation, O_EVTONLY)

        // Create our dispatch source
        let source = DispatchSource.makeFileSystemObjectSource(
            fileDescriptor: fileDescriptor,
            eventMask: .write,
            queue: queue
        )

        // Assign the closure to it, and resume it to start observing
        source.setEventHandler(handler: closure)
        source.resume()
        self.source = source
    }
}
复制代码

我们现在可以FileObserver像这样使用:

let observer = try FileObserver(file: file)

observer.start {
    print("File was changed")
}
复制代码

想象一下所有可以使用它构建的很酷的开发人员工具!?

结论

Grand Central Dispatch是一个非常强大的框架,它的功能远远超出它的初期状态。希望这篇文章能够激发你对它的用处的想象力,我建议你在下次需要完成我们在这篇文章中看到的任务之一时尝试一下。

在我看来,许多基于Timer或者OperationQueue的代码,以及第三方异步框架的使用,实际上可以通过直接使用GCD变得更简单。

小编这里有大量的书籍和面试资料哦(点击下载

原文地址 深入了解斯威夫特的Grand Central Dispatch

转载于:https://juejin.im/post/5d3aef3e6fb9a07ed36ef1c4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值