Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 从闭包到async/await的重构

从闭包到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带来了显著提升:

  1. 代码可读性
    从嵌套回调到线性流程,逻辑一目了然:

    // 重构前
    fetchUser { ... { ... { ... } } }
    // 重构后
    let user = try await fetchUser()
    let posts = try await fetchPosts(userId: user.id)
    
  2. 错误处理集中
    一个catch块捕获所有错误,避免重复检查:

    do { ... } catch { print(error) }
    
  3. 线程管理简化
    @MainActor自动处理主线程切换,无需手动DispatchQueue.main。

  4. 类型安全性
    返回值直接使用具体类型(如User),而非Result包装。

重构技巧与注意事项

  1. 识别异步点
    找到闭包回调的地方,通常是异步任务的完成点,替换为await调用。

  2. 处理遗留API
    如果API不支持异步(如老版本SDK),可以用withCheckedThrowingContinuation桥接:

    func fetchUserLegacy() async throws -> User {
        return try await withCheckedThrowingContinuation { continuation in
            fetchUser { result in
                continuation.resume(with: result)
            }
        }
    }
    
  3. 逐步迁移
    不必一次性重构所有代码,可先转换关键路径,保留闭包与async/await共存。

  4. 测试验证
    重构后需测试异常情况(如网络中断),确保错误处理生效。

小结

从闭包到async/await的重构将复杂的回调逻辑转化为简洁的异步流程,提升了代码质量和维护性。本节通过一个社交应用的案例,展示了重构的步骤和收益,并提供了实用技巧。掌握这一过程,你将能自信地将遗留代码升级为现代并发风格。下一章将深入探讨任务管理和并发控制,进一步扩展你的技能。


内容说明

  • 结构:从闭包代码到重构步骤,再到改进分析和技巧,最后总结。
  • 代码:完整示例展示重构前后对比,突出async/await优势。
  • 语气:实践性且指导性,适合技术书籍的过渡章节。
  • 衔接:承接前节(基本语法),预告后续(任务管理)。
Last Updated:: 3/4/25, 10:21 AM