基于 async/await 的 Swift 并发模型重构

一、重构核心价值

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):替代循环回调,处理流式数据(如网络流、日志流)。

四、总结

  1. async/await 重构的核心是将嵌套的回调逻辑转为线性代码 ,结合 try/catch 统一错误处理,大幅提升可读性;
  2. 替换 GCD 时,优先使用 Task/TaskGroup 实现结构化并发,用 Actor 替代手动加锁解决数据竞争;
  3. 重构后需注意主线程调度(@MainActor)、兼容性和 Sendable 协议,保证并发安全。

通过这套重构方式,异步代码的可维护性、可调试性和安全性会得到质的提升,这也是 Swift 并发模型的核心设计目标。

相关推荐
HouWan8 分钟前
SwiftUI 小技巧:如何读取 SwiftUI Font 的详细信息
ios·swiftui·apple
李游Leo26 分钟前
HarmonyOS 7 实战开发 05:把页面做到可上线状态
ios·harmonyos
终端安全笔记6 小时前
描述文件、企业证书、MDM 不是三个名字,是三层
android·ios·智能手机
传奇开心果编程6 小时前
【SwiftUI入门练中学】第1课 从零开始
学习·ui·ios·swiftui·swift
丿Wayne12 小时前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
终端安全笔记1 天前
iOS 27 给了租赁一个新工具,但它只认受监督的设备
android·网络·安全·ios
大熊猫侯佩1 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift
光影少年1 天前
setImmediate 和 setTimeout(0) 的区别
android·前端·react.js·ios·前端框架
2501_916008891 天前
SSE 和 gRPC 流式接口如何抓包,调试 SSE 与 gRPC 流式接口
网络协议·计算机网络·网络安全·ios·adb·https·udp
HouWan2 天前
Flutter: MediaQuery.of(context) 为什么可能拖慢页面?
android·flutter·ios