掌握从 GCD 到现代 Structured Concurrency 的范式演进,构建高性能、线程安全的 iOS 应用。
1. 结构化并发 (Structured Concurrency)
在 Swift 5.5+ 中,Apple 引入了全新的结构化并发模型。与传统的 GCD 不同,异步任务现在拥有了明确的父子关系和生命周期管理。
核心优势
- 自动取消传播:当父任务被取消时,所有子任务自动取消
- 资源自动释放:任务完成后,相关资源自动清理
- 编译时安全检查:编译器会检测潜在的数据竞争
Async/Await 基础
通过 async 标识函数为异步,await 标识潜在挂起点。代码读起来像同步代码一样流畅:
func fetchUserData() async throws -> User {
let url = URL(string: "https://api.example.com/user")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
// 调用异步函数
Task {
do {
let user = try await fetchUserData()
print("用户名: \(user.name)")
} catch {
print("获取失败: \(error)")
}
}
并行执行多个任务
使用 async let 可以并行执行多个独立的异步操作:
func fetchDashboardData() async throws -> Dashboard {
// 三个请求并行执行
async let user = fetchUser()
async let posts = fetchPosts()
async let notifications = fetchNotifications()
// 等待所有结果
return try await Dashboard(
user: user,
posts: posts,
notifications: notifications
)
}
TaskGroup 动态并发
当任务数量不确定时,使用 TaskGroup:
func fetchAllImages(urls: [URL]) async throws -> [UIImage] {
try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw ImageError.invalidData
}
return image
}
}
var images: [UIImage] = []
for try await image in group {
images.append(image)
}
return images
}
}
2. Actor 模型
Actor 是 Swift 5.5 引入的新引用类型,通过数据隔离确保线程安全。
基本使用
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) throws -> Double {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
return amount
}
func getBalance() -> Double {
return balance
}
}
// 使用 Actor
let account = BankAccount()
Task {
await account.deposit(100)
let balance = await account.getBalance()
print("余额: \(balance)")
}
Actor 隔离规则
- 内部访问:Actor 内部方法可以直接访问属性
- 外部访问:必须使用
await访问 Actor 的属性和方法 - nonisolated:标记不需要隔离的方法
actor DataManager {
private var cache: [String: Data] = [:]
// 需要隔离的方法
func store(_ data: Data, for key: String) {
cache[key] = data
}
// 不需要隔离的方法(只读计算属性)
nonisolated var description: String {
return "DataManager Instance"
}
}
@MainActor - 主线程隔离
用于确保 UI 操作在主线程执行:
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var isLoading = false
func loadItems() async {
isLoading = true
defer { isLoading = false }
do {
items = try await APIService.fetchItems()
} catch {
print("加载失败: \(error)")
}
}
}
3. 任务取消与超时
检查取消状态
func processLargeDataset(_ data: [Item]) async throws -> [Result] {
var results: [Result] = []
for item in data {
// 检查是否被取消
try Task.checkCancellation()
// 或者优雅地处理取消
if Task.isCancelled {
// 清理资源
break
}
let result = await process(item)
results.append(result)
}
return results
}
任务超时
func fetchWithTimeout() async throws -> Data {
try await withThrowingTaskGroup(of: Data.self) { group in
group.addTask {
try await fetchData()
}
group.addTask {
try await Task.sleep(nanoseconds: 5_000_000_000) // 5秒
throw TimeoutError.exceeded
}
// 返回第一个完成的结果
let result = try await group.next()!
group.cancelAll()
return result
}
}
4. Sendable 协议
Sendable 标记类型可以安全地跨并发域传递:
// 值类型自动 Sendable
struct UserData: Sendable {
let id: UUID
let name: String
}
// 类需要显式标记并确保线程安全
final class Configuration: Sendable {
let apiKey: String // 只有 let 属性
let baseURL: URL
init(apiKey: String, baseURL: URL) {
self.apiKey = apiKey
self.baseURL = baseURL
}
}
// Actor 自动 Sendable
actor Counter: Sendable {
private var count = 0
func increment() { count += 1 }
}
5. 性能对比
| 维度 | GCD | Swift Concurrency |
|---|---|---|
| 线程模型 | 每个任务可能新建线程 | 固定的协作式线程池 |
| 上下文切换 | 频繁,开销大 | 轻量级任务切换 |
| 内存安全 | 需手动处理 Data Race | 编译器强制检查 |
| 取消机制 | 需手动实现 | 内置结构化取消 |
| 代码可读性 | 嵌套回调 | 线性同步风格 |
| 调试体验 | 堆栈难以追踪 | 完整的异步堆栈 |
6. 最佳实践
✅ 推荐做法
// 1. 使用 async/await 替代回调
func loadData() async throws -> Data {
try await URLSession.shared.data(from: url).0
}
// 2. 使用 Actor 保护共享状态
actor Cache {
private var storage: [String: Data] = [:]
func get(_ key: String) -> Data? { storage[key] }
func set(_ key: String, data: Data) { storage[key] = data }
}
// 3. 在视图模型中使用 @MainActor
@MainActor
class ViewModel: ObservableObject {
@Published var data: [Item] = []
}
❌ 避免的做法
// 1. 不要在 Actor 中执行长时间同步操作
actor BadActor {
func processSync() {
// ❌ 这会阻塞 Actor
Thread.sleep(forTimeInterval: 5)
}
}
// 2. 不要忽略取消检查
func badLoop() async {
for i in 0..<1000000 {
// ❌ 没有检查取消状态
await heavyWork(i)
}
}
// 3. 不要过度使用 Task.detached
Task.detached {
// ❌ 失去结构化并发的优势
}
7. 迁移指南
从 GCD 迁移到 Swift Concurrency:
// 旧代码 - GCD
func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {
DispatchQueue.global().async {
// 网络请求...
DispatchQueue.main.async {
completion(.success(user))
}
}
}
// 新代码 - Swift Concurrency
func fetchUser() async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
兼容性桥接
使用 withCheckedContinuation 包装旧的回调 API:
func fetchUserAsync() async throws -> User {
try await withCheckedThrowingContinuation { continuation in
fetchUser { result in
continuation.resume(with: result)
}
}
}