一、重构核心价值
Swift 5.5 引入的 async/await 彻底改变了异步代码的编写方式:
- 替代传统的闭包回调(Callback Hell)和 GCD 嵌套,让异步代码"同步化书写";
- 结合
try/catch统一错误处理,替代闭包中"Result + Error"的分散式处理; - 配合
Actor/Sendable解决多线程数据竞争,简化线程安全逻辑; - 编译器层面的并发检查,减少运行时崩溃。
重构的核心目标:在保持功能等价的前提下,将回调式/ GCD 式异步逻辑,重构为基于 async/await 的结构化并发代码。
二、典型重构场景与实战示例
场景1:网络请求 + 本地缓存(闭包回调 → async/await)
1. 重构前(闭包回调风格,典型"回调地狱")
swift
import Dispatch
// 错误定义
enum NetworkError: Error {
case requestFailed
case cacheFailed
case noData
}
// 1. 本地缓存读取(闭包回调)
func readCache(key: String, completion: @escaping (Data?, Error?) -> Void) {
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 0.5) // 模拟耗时
let cacheData = UserDefaults.standard.data(forKey: key)
if let data = cacheData {
completion(data, nil)
} else {
completion(nil, NetworkError.cacheFailed)
}
}
}
// 2. 网络请求(闭包回调)
func fetchData(url: String, completion: @escaping (Data?, Error?) -> Void) {
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 1.0) // 模拟网络耗时
guard url.starts(with: "https") else {
completion(nil, NetworkError.requestFailed)
return
}
// 模拟返回数据
let mockData = "Mock Network Data".data(using: .utf8)!
completion(mockData, nil)
}
}
// 3. 业务逻辑(嵌套回调,可读性差)
func loadData(url: String, cacheKey: String, completion: @escaping (String?, Error?) -> Void) {
// 先读缓存
readCache(key: cacheKey) { cacheData, cacheError in
if let data = cacheData, let content = String(data: data, encoding: .utf8) {
completion(content, nil)
return
}
// 缓存失败,请求网络
fetchData(url: url) { networkData, networkError in
if let data = networkData, let content = String(data: data, encoding: .utf8) {
// 缓存网络数据
UserDefaults.standard.set(data, forKey: cacheKey)
completion(content, nil)
} else {
completion(nil, networkError ?? NetworkError.noData)
}
}
}
}
// 调用(多层嵌套,错误处理分散)
loadData(url: "https://example.com", cacheKey: "example_data") { content, error in
if let content = content {
print("加载成功:\(content)")
} else {
print("加载失败:\(error!)")
}
}
2. 重构后(async/await 风格,线性代码)
swift
// 1. 重构缓存读取:async 函数
func readCache(key: String) async throws -> Data {
// 切换到后台线程(Task 替代 GCD)
return try await Task.detached(priority: .utility) {
Thread.sleep(forTimeInterval: 0.5) // 模拟耗时
guard let cacheData = UserDefaults.standard.data(forKey: key) else {
throw NetworkError.cacheFailed
}
return cacheData
}.value
}
// 2. 重构网络请求:async 函数
func fetchData(url: String) async throws -> Data {
return try await Task.detached(priority: .utility) {
Thread.sleep(forTimeInterval: 1.0) // 模拟网络耗时
guard url.starts(with: "https") else {
throw NetworkError.requestFailed
}
return "Mock Network Data".data(using: .utf8)!
}.value
}
// 3. 重构业务逻辑:线性代码,try/catch 统一错误处理
func loadData(url: String, cacheKey: String) async throws -> String {
// 先读缓存
do {
let cacheData = try await readCache(key: cacheKey)
guard let content = String(data: cacheData, encoding: .utf8) else {
throw NetworkError.noData
}
return content
} catch NetworkError.cacheFailed {
// 缓存失败,请求网络
let networkData = try await fetchData(url: url)
guard let content = String(data: networkData, encoding: .utf8) else {
throw NetworkError.noData
}
// 缓存网络数据
UserDefaults.standard.set(networkData, forKey: cacheKey)
return content
}
}
// 调用(async/await + try/catch,简洁清晰)
Task {
do {
let content = try await loadData(url: "https://example.com", cacheKey: "example_data")
print("加载成功:\(content)")
} catch {
print("加载失败:\(error)")
}
}
场景2:GCD 批量任务 → async/await 并发任务
重构前(GCD 组任务)
swift
// 批量下载图片(GCD Group)
func downloadImages(urls: [String], completion: @escaping ([Data]) -> Void) {
let group = DispatchGroup()
var images = [Data]()
let queue = DispatchQueue.global()
for url in urls {
group.enter()
queue.async {
Thread.sleep(forTimeInterval: 0.5) // 模拟下载
images.append("Mock Image Data for \(url)".data(using: .utf8)!)
group.leave()
}
}
group.notify(queue: .main) {
completion(images)
}
}
// 调用
downloadImages(urls: ["url1", "url2", "url3"]) { images in
print("下载完成,共\(images.count)张")
}
重构后(async/await + TaskGroup)
swift
// 单张图片下载
func downloadImage(url: String) async -> Data {
await Task.sleep(500_000_000) // 模拟0.5秒耗时
return "Mock Image Data for \(url)".data(using: .utf8)!
}
// 批量下载(TaskGroup 替代 GCD Group)
func downloadImages(urls: [String]) async -> [Data] {
return await withTaskGroup(of: Data.self) { group in
// 提交所有任务
for url in urls {
group.addTask {
await downloadImage(url: url)
}
}
// 收集结果
var images = [Data]()
for await imageData in group {
images.append(imageData)
}
return images
}
}
// 调用
Task {
let images = await downloadImages(urls: ["url1", "url2", "url3"])
print("下载完成,共\(images.count)张")
}
场景3:解决数据竞争(GCD 加锁 → Actor)
重构前(GCD + 锁,手动保证线程安全)
swift
import Foundation
class Counter {
private var count = 0
private let lock = NSLock() // 手动加锁
// 异步自增(GCD)
func increment(completion: @escaping (Int) -> Void) {
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
self.lock.lock()
self.count += 1
let current = self.count
self.lock.unlock()
completion(current)
}
}
}
// 调用(多线程调用,依赖锁保证安全)
let counter = Counter()
for _ in 0..<10 {
counter.increment { count in
print("当前计数:\(count)")
}
}
重构后(Actor + async/await,编译器保证线程安全)
swift
// Actor 自动隔离状态,无需手动加锁
actor Counter {
private var count = 0
// async 方法,自动保证线程安全
func increment() async -> Int {
count += 1
return count
}
}
// 调用(多任务调用,无数据竞争)
Task {
let counter = Counter()
for _ in 0..<10 {
let count = await counter.increment()
print("当前计数:\(count)")
}
}
三、重构关键规则与注意事项
1. 核心语法转换
| 传统方式 | async/await 替代方案 |
|---|---|
| 闭包回调 | 标记函数为 async,调用时加 await |
GCD 异步任务(async) |
Task.detached(priority:) |
| GCD Group | withTaskGroup(of:body:) |
| 手动加锁(NSLock) | Actor 隔离状态 |
| 闭包内错误回调 | try/catch 捕获 async throws 错误 |
2. 避坑指南
- 主线程调用 :UI 操作必须切回主线程,使用
Task { @MainActor in ... }或标记方法为@MainActor; - 取消处理 :传统闭包取消需手动管理,
async/await可通过Task.cancel()配合try Task.checkCancellation()实现; - 兼容性 :
async/await要求 Swift 5.5+,iOS 15+/macOS 12+,低版本需做兼容封装; - Sendable 检查 :跨 Task 传递的对象需遵循
Sendable协议,避免隐式数据竞争。
3. 进阶优化
- 优先级管理 :
Task(priority: .userInitiated)替代 GCD 的qos,更贴合业务语义; - 结构化并发 :使用
TaskGroup替代分散的 GCD 任务,便于统一管理生命周期; - 异步序列(AsyncSequence):替代循环回调,处理流式数据(如网络流、日志流)。
四、总结
async/await重构的核心是将嵌套的回调逻辑转为线性代码 ,结合try/catch统一错误处理,大幅提升可读性;- 替换 GCD 时,优先使用
Task/TaskGroup实现结构化并发,用Actor替代手动加锁解决数据竞争; - 重构后需注意主线程调度(
@MainActor)、兼容性和Sendable协议,保证并发安全。
通过这套重构方式,异步代码的可维护性、可调试性和安全性会得到质的提升,这也是 Swift 并发模型的核心设计目标。