基于 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 并发模型的核心设计目标。

相关推荐
HiDev_12 小时前
iOS 蓝牙开发进阶:彻底理解 CBManager(状态、权限与正确使用方式)
ios·objective-c·蓝牙·ble
文件夹__iOS1 天前
SwiftUI 核心选型:class + ObservableObject VS struct + @State
ios·swiftui·swift
SameX1 天前
独立开发了一款健康记录 App,聊聊几个让我纠结很久的设计决策
ios
报错小能手1 天前
Swift UI 框架 实战 简易计数器、待办清单 、随机壁纸图库、个人笔记
ui·ios
游戏开发爱好者81 天前
深入理解iOSTime Profiler:提升iOS应用性能的关键工具
android·ios·小程序·https·uni-app·iphone·webview
for_ever_love__2 天前
UI学习:多界面传值的正向传值(属性传值)和反向传值(代理传值)
学习·ui·ios·objective-c
开心就好20252 天前
全面介绍iOS开发工具:Xcode、AppCode、CocoaPods、Fastlane和Git
后端·ios
懋学的前端攻城狮2 天前
数据持久化与缓存策略:在离线与在线间架起桥梁
ios·swift
~央千澈~2 天前
以cocos3.8.8开发的游戏为例商业实战项目举例cocos打包ios苹果安装包ipa完整详细教程-优雅草卓伊凡
ios
SameX2 天前
iOS 足迹 App 的成就系统,我推倒重做了一次——踩了3个坑之后
ios