Skip to content

并发控制

使用 Swift 进行 iOS 开发 - 并发控制

Section titled “使用 Swift 进行 iOS 开发 - 并发控制”

并发 (Concurrency) 允许应用程序同时执行多个任务。在 iOS 中,这对于在后台执行网络请求、数据处理或复杂计算等耗时操作时,保持用户界面 (UI - User Interface) 的响应性至关重要。如果没有适当的并发管理,这些任务可能会冻结 UI,导致糟糕的用户体验。

Swift 提供了几种机制来管理并发任务。我们将主要讨论 Grand Central Dispatch (GCD) 和现代 Swift 并发模型 (async/await)。

GCD 是一个低级的基于 C 的 API,在 Swift 中公开使用,用于管理并发代码并异步地 (asynchronously) 执行操作。它使用“调度队列 (dispatch queues)”,这是一种数据结构,以先进先出 (FIFO - First-In, First-Out) 的顺序管理任务。

想象一下队列就像电影售票处的队伍:第一个排队的人先得到服务。提交给调度队列的任务根据队列的类型和系统资源来执行。

  • 串行队列 (Serial Queues):一次执行一个任务,按照添加的顺序。每个任务必须在下一个任务开始之前完成。适用于保护共享资源或执行顺序至关重要的情况。
  • 并行队列 (Concurrent Queues):可以同时执行多个任务(最多可达系统的核心容量)。任务仍然按照添加的顺序开始,但它们可以并行运行并以任意顺序完成。

GCD 提供了几种标准队列:

  • DispatchQueue.main:一个串行队列,在应用程序的主线程 (main thread) 上执行任务。所有 UI 更新必须在此队列上执行。
  • 全局并行队列 (Global Concurrent Queues):系统提供的并行队列,具有不同的服务质量 (QoS - Quality of Service) 等级(例如,.userInitiated、.background、.utility、.userInteractive)。QoS 有助于系统确定任务的优先级。

您也可以创建自定义的串行或并行队列:

// Custom serial queue
let mySerialQueue = DispatchQueue(label: "com.myapp.mySerialQueue")
// Custom concurrent queue
let myConcurrentQueue = DispatchQueue(label: "com.myapp.myConcurrentQueue", attributes: .concurrent)

任务作为代码块(闭包 - closures)提交给队列。

  • async:将任务提交到队列并立即返回(异步)。调用线程不会等待任务完成。这用于将工作分派到后台线程。
  • sync:将任务提交到队列并等待其完成后才返回(同步)。在主线程上使用 sync 调用主队列,或在串行队列上调用自身,请务必小心,因为它可能导致死锁 (deadlocks)。

一个常见的模式是在后台队列上执行耗时任务,然后在主队列上更新 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 5.5 中引入的 async/await 提供了一种更现代、更易读的方式来编写异步代码。它允许您像编写同步操作一样编写异步操作,而无需复杂的嵌套闭包(回调地狱 - callback hell)。

标记为 async 的函数在调用其他 async 函数时可以使用 await 进行“暂停”。当被 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 流畅且响应迅速。