Swift Concurrency (async/await) 是 Swift 5.5 引入的一套新的并发编程模型,旨在让异步代码的编写更加简单、安全、可读。它引入了 async/await 语法、结构化并发、Task 和 Actor 等概念。
1. async/await 基础
async 函数
使用 async 关键字标记一个函数是异步的,这意味着该函数可以挂起自己的执行,等待异步操作完成。
swift
func fetchData(from url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
await 调用
在调用异步函数时,使用 await 关键字。这表示当前点可以挂起,等待异步函数完成。注意,await 并不阻塞线程,而是允许线程去执行其他任务,直到异步操作完成。
Swift
swift
Task {
do {
let data = try await fetchData(from: someURL)
print("收到数据: \(data)")
} catch {
print("错误: \(error)")
}
}
2. Task
Task 是并发执行的基本单位,它代表一个异步任务。创建一个 Task 即启动一个并发任务。
创建任务
Swift
swift
Task {
// 异步代码
let result = await someAsyncFunction()
print(result)
}
取消任务
任务可以被取消,通过检查 Task.isCancelled 或调用 Task.cancel()。
Swift
swift
let task = Task {
// 定期检查是否取消
while !Task.isCancelled {
await doSomeWork()
}
}
// 取消任务
task.cancel()
结构化并发
结构化并发是指任务的生命周期与创建它的作用域绑定,当作用域退出时,所有子任务都会被取消并等待完成。这通过任务组(Task Group)来实现。
Swift
swift
func processMultipleRequests() async {
await withTaskGroup(of: Data.self) { group in
for url in urls {
group.addTask {
return try await fetchData(from: url)
}
}
// 收集结果
for await result in group {
process(result)
}
}
}
3. Actor
Actor 是 Swift Concurrency 中用于保护共享状态的一种类型。它通过隔离(isolation)来确保在同一时刻只有一个任务可以访问其可变状态,从而避免数据竞争(data race)。
定义 Actor
Swift
swift
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Bool {
if balance >= amount {
balance -= amount
return true
} else {
return false
}
}
func currentBalance() -> Double {
return balance
}
}
使用 Actor
因为 Actor 的方法和属性是隔离的,所以访问它们需要使用 await,表示可能会挂起等待访问权。
Swift
swift
let account = BankAccount(initialBalance: 1000)
Task {
await account.deposit(amount: 200)
let balance = await account.currentBalance()
print("当前余额: \(balance)")
}
Actor 的重入(Reentrancy)
Actor 可以处理多个请求,但一次只处理一个。当 Actor 中的一个方法挂起(例如,调用了另一个异步函数)时,Actor 可以处理另一个任务。这可能导致重入,即同一个 Actor 方法在挂起后再次被调用。因此,在设计 Actor 时,需要注意在挂起前保持状态一致,并在恢复后检查状态是否改变。
防止数据竞争
在没有 Actor 的情况下,多个线程同时访问和修改同一个变量会导致数据竞争。例如:
Swift
swift
class NonSafeCounter {
var value = 0
func increment() {
value += 1
}
}
如果多个线程同时调用 increment(),value 的最终值可能不确定。而使用 Actor 可以安全地管理这种状态。
MainActor
MainActor 是一个特殊的 Actor,它运行在主线程上。任何需要在主线程上执行的操作(如更新 UI)都可以通过标记为 @MainActor 来确保在主线程上运行。
Swift
swift
@MainActor
func updateUI() {
// 更新UI控件
}
Task {
// 在后台执行异步操作
let data = await fetchData()
// 回到主线程更新UI
await updateUI()
}
或者将整个类标记为运行在主线程:
Swift
swift
@MainActor
class MyViewController: UIViewController {
// 所有属性和方法都会在主线程上访问
}
4. 异步序列(AsyncSequence)
异步序列允许我们以异步的方式迭代序列中的元素。
Swift
swift
let urls = [URL(string: "https://example.com/1")!, URL(string: "https://example.com/2")!]
for try await data in urls.async.compactMap({ $0 }).map({ try await fetchData(from: $0) }) {
process(data)
}
5. 异步属性(Async Properties)
除了函数,属性也可以是异步的。
Swift
swift
actor SomeActor {
var someProperty: String {
get async {
await someAsyncMethod()
return "value"
}
}
}
总结
Swift Concurrency 提供了一套现代化、安全且高效的并发编程工具。async/await 使得异步代码的编写和阅读更加直观;Task 和结构化并发确保了任务的生命周期管理;Actor 则解决了共享状态下的数据竞争问题。
使用建议:
-
1、尽量使用 async/await 替代回调(completion handlers)和 Future/Promise。
-
2、对于需要保护共享状态的场景,使用 Actor。
-
3、UI 更新务必使用 MainActor 确保在主线程上进行。
-
4、利用结构化并发来管理多个并发任务,避免任务泄漏。
通过这些工具,Swift 开发者可以编写出更安全、更易维护的并发代码。
Swift Concurrency (async/await) 详解
一、核心概念
1. async/await 基础
Swift
swift
// 传统回调方式 → 现代 async/await
func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
// 回调地狱
}
// 转换为 async/await
func fetchData() async throws -> String {
// 直接返回结果
}
2. 基本使用
Swift
swift
// 1. 声明异步函数
func downloadImage(from url: URL) async throws -> UIImage {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw DownloadError.invalidData
}
return image
}
// 2. 调用异步函数
Task {
do {
let image = try await downloadImage(from: imageURL)
// 自动切换到主线程更新UI
await MainActor.run {
self.imageView.image = image
}
} catch {
print("下载失败: \(error)")
}
}
二、Task 系统
1. Task 的类型
Swift
swift
// 1. 非结构化任务
func startTasks() {
// 创建并立即执行
Task {
let result1 = await fetchData1()
print(result1)
}
// 获取任务引用
let task = Task {
try await processData()
}
// 稍后取消
task.cancel()
}
// 2. 结构化并发 - withTaskGroup
func downloadAllImages(urls: [URL]) async throws -> [UIImage] {
try await withThrowingTaskGroup(of: UIImage.self) { group in
var images: [UIImage] = []
for url in urls {
group.addTask {
try await downloadImage(from: url)
}
}
// 按完成顺序收集结果
for try await image in group {
images.append(image)
}
return images
}
}
// 3. 结构化并发 - async let
func fetchUserData() async throws -> (User, [Post], [Photo]) {
async let user = fetchUser()
async let posts = fetchPosts()
async let photos = fetchPhotos()
// 并行执行,等待所有完成
return try await (user, posts, photos)
}
2. Task 优先级
Swift
swift
Task(priority: .userInitiated) {
// 高优先级任务
}
Task(priority: .background) {
// 低优先级后台任务
}
Task(priority: .medium) {
// 默认优先级
}
// 继承优先级
Task.detached {
// 不继承父任务优先级
}
3. 任务取消
Swift
swift
func processLargeFile() async throws {
// 1. 检查取消状态
try Task.checkCancellation()
// 2. 定期检查
for chunk in largeFile.chunks {
if Task.isCancelled {
print("任务被取消")
return
}
await process(chunk)
}
// 3. 使用 withTaskCancellationHandler
try await withTaskCancellationHandler {
// 执行任务
try await uploadFile()
} onCancel: {
// 取消时的清理工作
cleanup()
}
}
三、Actor 详解
1. Actor 的意义和问题解决
解决的问题:
-
数据竞争(Data Race):多个线程同时访问/修改同一数据
-
竞态条件(Race Condition):执行顺序不确定导致结果不一致
传统方案的问题:
Swift
swift
class Counter {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock()
defer { lock.unlock() }
value += 1 // 容易忘记加锁,手动管理复杂
}
}
2. Actor 的核心特性
Swift
swift
// 定义 Actor
actor BankAccount {
private var balance: Double = 0
private let accountNumber: String
init(accountNumber: String, initialBalance: Double = 0) {
self.accountNumber = accountNumber
self.balance = initialBalance
}
// Actor 方法默认是异步的
func deposit(amount: Double) {
balance += amount
print("存款 \(amount), 余额: \(balance)")
}
func withdraw(amount: Double) -> Bool {
guard balance >= amount else {
print("余额不足")
return false
}
balance -= amount
print("取款 \(amount), 余额: \(balance)")
return true
}
// 同步方法(非隔离)
nonisolated func getAccountNumber() -> String {
return accountNumber // accountNumber 是 let 常量,可以安全访问
}
// 计算属性
var currentBalance: Double {
get async {
await someAsyncOperation()
return balance
}
}
// Actor 内部可以同步访问自己的状态
func transfer(to otherAccount: BankAccount, amount: Double) async throws {
guard self.withdraw(amount: amount) else {
throw TransferError.insufficientFunds
}
await otherAccount.deposit(amount: amount)
}
}
3. Actor 隔离机制
Swift
swift
actor DataManager {
private var cache: [String: Data] = [:]
private var accessLog: [String] = []
// 1. 隔离方法 - 只能异步访问
func getData(for key: String) async -> Data? {
accessLog.append("读取: \(key)")
return cache[key]
}
// 2. nonisolated 方法 - 可以同步访问
nonisolated func getCacheKeys() -> [String] {
return Array(cache.keys) // 错误!不能访问隔离属性
// 正确:返回非隔离数据
return ["key1", "key2"]
}
// 3. Actor 内部的同步访问
func updateCache(key: String, value: Data) {
// 可以直接访问隔离属性
cache[key] = value
accessLog.append("更新: \(key)")
// 也可以调用其他隔离方法
cleanupOldEntries()
}
private func cleanupOldEntries() {
// 私有方法也自动隔离
}
}
4. MainActor
Swift
swift
// 1. 标记整个类在主线程运行
@MainActor
class ViewModel: ObservableObject {
@Published var data: [String] = []
func loadData() async {
// 这个方法会自动在主线程执行
let result = await fetchData()
data = result // 更新 @Published 属性会自动在主线程
}
}
// 2. 标记单个方法
class DataLoader {
@MainActor func updateUI(with data: Data) {
// 确保在主线程更新UI
imageView.image = UIImage(data: data)
}
func processData() async {
let data = await fetchData()
await updateUI(with: data) // 需要 await
}
}
// 3. 使用 MainActor.run
Task {
let data = await fetchData()
await MainActor.run {
// 这个闭包在主线程执行
self.label.text = "完成"
self.imageView.image = UIImage(data: data)
}
}
5. Sendable 协议
Swift
swift
// 1. 可安全跨并发域传递的类型
struct User: Sendable {
let id: String
let name: String
// 所有属性都必须是 Sendable 的
}
// 2. Actor 自动遵循 Sendable
actor Logger: Sendable {
// Actor 自动是 Sendable
}
// 3. 在异步函数中使用
func processUser(user: User) async {
// User 是 Sendable,可以安全传递
await logUser(user: user)
}
// 4. @Sendable 闭包
Task {
let result = await withCheckedContinuation { continuation in
someAsyncOperation { value in
continuation.resume(returning: value)
}
}
}
6. Actor 重入问题
Swift
swift
actor BankAccount {
private var balance: Double = 1000
func withdraw(amount: Double) async -> Bool {
// 第一次检查
guard balance >= amount else { return false }
// 模拟异步操作(此时可能被其他任务中断)
await someNetworkCall()
// 重新进入后再次检查
guard balance >= amount else { return false }
balance -= amount
return true
}
}
// 解决方案:使用锁或标记
actor SafeAccount {
private var balance: Double = 1000
private var isProcessing = false
func safeWithdraw(amount: Double) async throws -> Bool {
guard !isProcessing else {
throw AccountError.operationInProgress
}
isProcessing = true
defer { isProcessing = false }
guard balance >= amount else { return false }
await someNetworkCall()
guard balance >= amount else { return false }
balance -= amount
return true
}
}
四、实际应用模式
1. Repository 模式 + Actor
Swift
swift
actor UserRepository {
private var cache: [String: User] = [:]
private let apiService: APIService
init(apiService: APIService) {
self.apiService = apiService
}
func getUser(by id: String) async throws -> User {
// 1. 检查缓存
if let cached = cache[id] {
return cached
}
// 2. 网络请求
let user = try await apiService.fetchUser(id: id)
// 3. 更新缓存
cache[id] = user
return user
}
func updateUser(_ user: User) async throws {
// 1. 更新缓存
cache[user.id] = user
// 2. 同步到服务器
try await apiService.updateUser(user)
}
func clearCache() {
cache.removeAll()
}
}
2. ViewModel + @MainActor
Swift
swift
@MainActor
class UserViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
@Published var error: Error?
private let repository: UserRepository
init(repository: UserRepository) {
self.repository = repository
}
func loadUsers() async {
isLoading = true
defer { isLoading = false }
do {
// 这里在后台线程执行
let loadedUsers = try await repository.getAllUsers()
// 自动切换到主线程更新 @Published 属性
self.users = loadedUsers
} catch {
self.error = error
}
}
func addUser(_ user: User) async {
do {
try await repository.addUser(user)
await loadUsers() // 刷新列表
} catch {
self.error = error
}
}
}
3. 并发模式组合
Swift
swift
actor ImageCache {
private var images: [String: UIImage] = [:]
private let fileManager = FileManager.default
func getImage(for key: String) async -> UIImage? {
// 1. 内存缓存
if let cached = images[key] {
return cached
}
// 2. 磁盘缓存
if let diskImage = await loadFromDisk(key: key) {
images[key] = diskImage
return diskImage
}
return nil
}
func setImage(_ image: UIImage, for key: String) async {
// 更新内存缓存
images[key] = image
// 异步保存到磁盘
Task.detached(priority: .background) {
await self.saveToDisk(image: image, key: key)
}
}
private func saveToDisk(image: UIImage, key: String) async {
// 磁盘操作
}
private func loadFromDisk(key: String) async -> UIImage? {
// 磁盘读取
return nil
}
}
五、最佳实践
1. 错误处理
Swift
swift
enum DataError: Error {
case notFound
case networkError
case decodingError
}
func fetchData() async throws -> DataModel {
do {
return try await apiService.fetch()
} catch URLError.badURL {
throw DataError.networkError
} catch DecodingError.dataCorrupted {
throw DataError.decodingError
}
}
// 使用 Task 处理错误
Task {
do {
let data = try await fetchData()
await updateUI(data)
} catch DataError.networkError {
await showNetworkError()
} catch {
await showGenericError(error)
}
}
2. 性能优化
Swift
swift
// 1. 限制并发数量
func processItems(_ items: [Item]) async {
await withTaskGroup(of: Void.self) { group in
let semaphore = AsyncSemaphore(value: 5) // 最多5个并发
for item in items {
await semaphore.wait()
group.addTask {
defer { semaphore.signal() }
await process(item)
}
}
await group.waitForAll()
}
}
// 2. 使用 continuation 包装回调代码
func fetchWithContinuation() async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
oldAsyncFunction { result in
switch result {
case .success(let data):
continuation.resume(returning: data)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
3. 测试
Swift
swift
// 1. 测试异步函数
func testAsyncFunction() async throws {
let result = try await someAsyncFunction()
XCTAssertEqual(result, expectedValue)
}
// 2. 模拟 actor
actor MockRepository: UserRepositoryProtocol {
private var users: [User] = []
func addUser(_ user: User) async {
users.append(user)
}
func getAllUsers() async -> [User] {
return users
}
}
六、注意事项
-
避免阻塞调用:不要在 async 函数中使用同步阻塞操作
-
合理使用 Task:避免创建过多不必要的 Task
-
注意 Actor 重入:在挂起点前后状态可能已改变
-
内存管理:注意循环引用,特别是 Task 中捕获 self
-
优先级继承:默认情况下,子任务继承父任务优先级
-
调试:使用 Instruments 的 Swift Concurrency 模板分析
Swift Concurrency 通过编译器级别的安全检查,大大减少了并发编程中的常见错误,是 Swift 并发编程的未来方向。