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

相关推荐
leluckys10 小时前
swift- Swift中常见的面试题
开发语言·汇编·swift
泉木18 小时前
Swift 从入门到精通-第四篇
swift
泉木18 小时前
Swift 从入门到精通-终篇
swift
ITKEY_21 小时前
appstore上架-预览和截屏
ios·appstore
阿捏利1 天前
详解Mach-O(三十三)Mach-O __mod_term_func节
macos·ios·c/c++·mach-o
2501_916007471 天前
提高开发效率的尝试,用快蝎(kxapp)完成 iOS 项目的创建、调试与构建
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915106321 天前
如何在 Mac 上面代理抓包和数据流分析
android·macos·ios·小程序·uni-app·iphone·webview
blackorbird1 天前
Coruna 间谍软件活动持续扩散,苹果破例为旧版iOS设备推送双版本安全补丁
macos·ios·objective-c·cocoa
for_ever_love__1 天前
Objective-C 学习 单例模式
学习·ios·单例模式·objective-c