从闭包到async/await的重构
Swift 5.5引入async/await后,许多开发者面临一个现实问题:如何将现有的闭包回调代码迁移到现代并发模型?这一过程不仅能提升代码可读性,还能优化错误处理和任务管理。本节将通过一个实际案例,逐步展示从闭包到async/await的重构方法,分析重构前后的差异,并总结关键技巧,帮助你在项目中顺利完成技术升级。
重构前的闭包代码
假设我们有一个社交应用,需要从服务器获取用户信息、帖子列表和头像图片,传统闭包实现可能如下:
func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {
URLSession.shared.dataTask(with: URL(string: "https://api.example.com/user")!) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else { return }
do {
let user = try JSONDecoder().decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
}.resume()
}
func fetchPosts(userId: String, completion: @escaping (Result<[Post], Error>) -> Void) {
let url = URL(string: "https://api.example.com/posts?user=\(userId)")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else { return }
do {
let posts = try JSONDecoder().decode([Post].self, from: data)
completion(.success(posts))
} catch {
completion(.failure(error))
}
}.resume()
}
func fetchAvatar(url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data, let image = UIImage(data: data) else { return }
completion(.success(image))
}.resume()
}
// 使用示例
func updateProfile() {
fetchUser { [weak self] result in
switch result {
case .success(let user):
self?.fetchPosts(userId: user.id) { postsResult in
switch postsResult {
case .success(let posts):
self?.fetchAvatar(url: user.avatarURL) { avatarResult in
switch avatarResult {
case .success(let avatar):
DispatchQueue.main.async {
self?.displayProfile(user: user, posts: posts, avatar: avatar)
}
case .failure(let error):
print("头像加载失败:\(error)")
}
}
case .failure(let error):
print("帖子加载失败:\(error)")
}
}
case .failure(let error):
print("用户加载失败:\(error)")
}
}
}
这个代码存在典型问题:
- 嵌套深:三层回调形成“金字塔式”结构。
- 错误分散:每个函数单独处理错误,逻辑重复。
- 线程切换手动:需显式使用
DispatchQueue.main。
重构步骤
将上述代码重构为async/await,可以按以下步骤进行:
1. 将闭包函数改为异步函数
将每个函数改为返回结果而非通过闭包回调,使用async和throws:
func fetchUser() async throws -> User {
let (data, _) = try await URLSession.shared.data(from: URL(string: "https://api.example.com/user")!)
return try JSONDecoder().decode(User.self, from: data)
}
func fetchPosts(userId: String) async throws -> [Post] {
let url = URL(string: "https://api.example.com/posts?user=\(userId)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Post].self, from: data)
}
func fetchAvatar(url: URL) async throws -> UIImage {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else { throw ImageError.invalidData }
return image
}
- 使用
URLSession.shared.data(from:)的异步版本,替代闭包API。 - 抛出错误而非用
Result封装。
2. 重构调用逻辑
将嵌套回调改为线性流程,使用Task启动异步任务:
func updateProfile() {
Task { [weak self] in
do {
let user = try await fetchUser()
let posts = try await fetchPosts(userId: user.id)
let avatar = try await fetchAvatar(url: user.avatarURL)
await self?.displayProfile(user: user, posts: posts, avatar: avatar)
} catch {
print("加载失败:\(error)")
}
}
}
@MainActor
func displayProfile(user: User, posts: [Post], avatar: UIImage) {
self.label.text = user.name
self.tableView.reloadData(with: posts)
self.imageView.image = avatar
}
- 用
do/catch统一捕获错误。 @MainActor确保UI更新在主线程。
3. 定义辅助类型
为错误处理添加自定义类型(可选):
enum ImageError: Error {
case invalidData
}
重构后的改进
对比重构前后,async/await带来了显著提升:
代码可读性
从嵌套回调到线性流程,逻辑一目了然:// 重构前 fetchUser { ... { ... { ... } } } // 重构后 let user = try await fetchUser() let posts = try await fetchPosts(userId: user.id)错误处理集中
一个catch块捕获所有错误,避免重复检查:do { ... } catch { print(error) }线程管理简化
@MainActor自动处理主线程切换,无需手动DispatchQueue.main。类型安全性
返回值直接使用具体类型(如User),而非Result包装。
重构技巧与注意事项
识别异步点
找到闭包回调的地方,通常是异步任务的完成点,替换为await调用。处理遗留API
如果API不支持异步(如老版本SDK),可以用withCheckedThrowingContinuation桥接:func fetchUserLegacy() async throws -> User { return try await withCheckedThrowingContinuation { continuation in fetchUser { result in continuation.resume(with: result) } } }逐步迁移
不必一次性重构所有代码,可先转换关键路径,保留闭包与async/await共存。测试验证
重构后需测试异常情况(如网络中断),确保错误处理生效。
小结
从闭包到async/await的重构将复杂的回调逻辑转化为简洁的异步流程,提升了代码质量和维护性。本节通过一个社交应用的案例,展示了重构的步骤和收益,并提供了实用技巧。掌握这一过程,你将能自信地将遗留代码升级为现代并发风格。下一章将深入探讨任务管理和并发控制,进一步扩展你的技能。
内容说明
- 结构:从闭包代码到重构步骤,再到改进分析和技巧,最后总结。
- 代码:完整示例展示重构前后对比,突出
async/await优势。 - 语气:实践性且指导性,适合技术书籍的过渡章节。
- 衔接:承接前节(基本语法),预告后续(任务管理)。
