并发控制
使用 Swift 进行 iOS 开发 - 并发控制
Section titled “使用 Swift 进行 iOS 开发 - 并发控制”并发 (Concurrency) 允许应用程序同时执行多个任务。在 iOS 中,这对于在后台执行网络请求、数据处理或复杂计算等耗时操作时,保持用户界面 (UI - User Interface) 的响应性至关重要。如果没有适当的并发管理,这些任务可能会冻结 UI,导致糟糕的用户体验。
Swift 提供了几种机制来管理并发任务。我们将主要讨论 Grand Central Dispatch (GCD) 和现代 Swift 并发模型 (async/await)。
Grand Central Dispatch (GCD)
Section titled “Grand Central Dispatch (GCD)”GCD 是一个低级的基于 C 的 API,在 Swift 中公开使用,用于管理并发代码并异步地 (asynchronously) 执行操作。它使用“调度队列 (dispatch queues)”,这是一种数据结构,以先进先出 (FIFO - First-In, First-Out) 的顺序管理任务。
想象一下队列就像电影售票处的队伍:第一个排队的人先得到服务。提交给调度队列的任务根据队列的类型和系统资源来执行。
调度队列的类型
Section titled “调度队列的类型”- 串行队列 (Serial Queues):一次执行一个任务,按照添加的顺序。每个任务必须在下一个任务开始之前完成。适用于保护共享资源或执行顺序至关重要的情况。
- 并行队列 (Concurrent Queues):可以同时执行多个任务(最多可达系统的核心容量)。任务仍然按照添加的顺序开始,但它们可以并行运行并以任意顺序完成。
GCD 提供了几种标准队列:
DispatchQueue.main:一个串行队列,在应用程序的主线程 (main thread) 上执行任务。所有 UI 更新必须在此队列上执行。- 全局并行队列 (Global Concurrent Queues):系统提供的并行队列,具有不同的服务质量 (QoS - Quality of Service) 等级(例如,
.userInitiated、.background、.utility、.userInteractive)。QoS 有助于系统确定任务的优先级。
您也可以创建自定义的串行或并行队列:
// Custom serial queuelet mySerialQueue = DispatchQueue(label: "com.myapp.mySerialQueue")
// Custom concurrent queuelet myConcurrentQueue = DispatchQueue(label: "com.myapp.myConcurrentQueue", attributes: .concurrent)提交任务到队列
Section titled “提交任务到队列”任务作为代码块(闭包 - closures)提交给队列。
async:将任务提交到队列并立即返回(异步)。调用线程不会等待任务完成。这用于将工作分派到后台线程。sync:将任务提交到队列并等待其完成后才返回(同步)。在主线程上使用sync调用主队列,或在串行队列上调用自身,请务必小心,因为它可能导致死锁 (deadlocks)。
GCD 示例:后台任务与 UI 更新
Section titled “GCD 示例:后台任务与 UI 更新”一个常见的模式是在后台队列上执行耗时任务,然后在主队列上更新 UI:
func performBackgroundTask() { // Get a global concurrent queue (background priority) // 获取一个全局并行队列(后台优先级) DispatchQueue.global(qos: .background).async { // Simulate a long-running task // 模拟一个耗时任务 print("Background task started on thread: \(Thread.current)") Thread.sleep(forTimeInterval: 3) // Simulate work for 3 seconds // 模拟工作 3 秒 let result = "Task Complete!" print("Background task finished.")
// Switch back to the main queue to update UI // 切换回主队列以更新 UI DispatchQueue.main.async { print("Updating UI on thread: \(Thread.current)") // self.myLabel.text = result // Example UI update // self.myLabel.text = result // 示例 UI 更新 } } print("performBackgroundTask method finished executing on thread: \(Thread.current)")}Swift 并发模型 (async/await)
Section titled “Swift 并发模型 (async/await)”Swift 5.5 中引入的 async/await 提供了一种更现代、更易读的方式来编写异步代码。它允许您像编写同步操作一样编写异步操作,而无需复杂的嵌套闭包(回调地狱 - callback hell)。
标记为 async 的函数在调用其他 async 函数时可以使用 await 进行“暂停”。当被 await 的函数正在执行时,系统可以将线程用于其他工作。
Async/Await 示例
Section titled “Async/Await 示例”以下是使用 async/await 获取数据并更新 UI 的示例:
@available(iOS 13.0, *)func fetchData() async -> String? { // This function itself is async // 此函数本身是 async 的 print("fetchData started on thread: \(Thread.current)") // Simulate a network request // 模拟一个网络请求 try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) // Sleep for 2 seconds // 休眠 2 秒 let fetchedData = "Data from server" print("fetchData finished on thread: \(Thread.current)") return fetchedData}
@available(iOS 13.0, *)func processAndDisplayData() { // Create a new Task to run asynchronous code from a synchronous context // 创建一个新的 Task,以便从同步上下文运行异步代码 Task { print("processAndDisplayData Task started on thread: \(Thread.current)") if let data = await fetchData() { // UI updates should be on the MainActor // UI 更新应该在 MainActor 上进行 await MainActor.run { print("Updating UI with data on thread: \(Thread.current)") // self.myLabel.text = data // Example UI update // self.myLabel.text = data // 示例 UI 更新 } } } print("processAndDisplayData method finished executing on thread: \(Thread.current)")}Swift 并发模型的关键概念:
async:标记函数为异步函数。await:暂停执行,直到async函数完成。Task:一个异步工作单元。您可以创建任务来运行async代码。Actor:一种引用类型,用于保护其可变状态免受并发访问 (concurrent access) 的影响。MainActor是一个特殊 actor,代表主线程 (main thread),确保 UI 更新是安全的。
虽然 GCD 仍然是基础且被广泛使用,但 Swift 并发模型 (async/await) 通常更受青睐,用于编写新的 Swift 异步代码,因为它具有更好的可读性和安全性。理解两者对于 iOS 开发者来说都有益处。
实践使用这些并发机制,尤其是在处理网络操作、文件 I/O 或任何可能耗时较长的任务时,以保持您的应用程序 UI 流畅且响应迅速。