Swift并发编程实战:闭包回调到Async/Await的完整迁移指南

216 阅读4分钟

本文通过对比传统闭包回调与Swift现代并发模型的实现差异,提供一套完整的代码迁移方案。你将掌握如何将网络请求、并行任务、数据流处理和线程安全机制全面升级到async/await范式,提升代码可读性和性能。


一、网络请求改造:消除回调地狱

传统闭包实现(金字塔问题)

func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) {
    let request = thumbnailURLRequest(for: id)
    let task = URLSession.shared.dataTask(with: request) { data, _, error in
        guard let data = data else { 
            completion(nil, error)
            return
        }
        
        UIImage(data: data)?.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
            guard let thumbnail = thumbnail else {
                completion(nil, FetchError.badImage)
                return
            }
            completion(thumbnail, nil)
        }
    }
    task.resume()
}

现代async/await方案(线性执行)

func fetchThumbnail(for id: String) async throws -> UIImage {
    let request = thumbnailURLRequest(for: id)
    let (data, response) = try await URLSession.shared.data(for: request)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw FetchError.badID
    }
    
    guard let image = UIImage(data: data),
          let thumbnail = await image.byPreparingThumbnail(ofSize: CGSize(width: 40, height: 40)) 
    else {
        throw FetchError.badImage
    }
    
    return thumbnail
}

关键改造点

  1. 使用await替代dataTask闭包
  2. 将嵌套缩略图生成闭包改为同步等待
  3. 错误处理升级为throw机制

二、并行任务处理:Task Group最佳实践

传统串行下载

// 存在顺序执行的性能瓶颈
func fetchAllThumbnails(ids: [String], completion: @escaping ([UIImage]) -> Void) {
    var results = [UIImage]()
    var remaining = ids.count
    
    for id in ids {
        fetchThumbnail(for: id) { image, _ in
            if let image = image {
                results.append(image)
            }
            remaining -= 1
            if remaining == 0 {
                completion(results)
            }
        }
    }
}

并行优化实现

func fetchThumbnails(ids: [String]) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: UIImage.self) { group in
        // 批量添加并发任务
        ids.forEach { id in
            group.addTask { try await self.fetchThumbnail(for: id) }
        }
        
        // 实时收集结果
        return try await group.reduce(into: []) { $0.append($1) }
    }
}

性能提升点

  1. 并行执行所有下载任务
  2. 使用reduce自动处理结果收集
  3. 内置错误传播机制

三、数据流处理:AsyncStream实战升级

闭包式进度监控

class Downloader {
    func download(url: URL, 
                 progress: @escaping (Float) -> Void,
                 completion: @escaping (Result<Data, Error>) -> Void)
}

// 使用示例
Downloader().download(url: url, 
    progress: { print("进度:($0)") }, 
    completion: { result in /*...*/ }
)

异步流式改造

extension Downloader {
    func downloadStream(for url: URL) -> AsyncThrowingStream<DownloadStatus, Error> {
        AsyncThrowingStream { continuation in
            do {
                try self.download(url: url,
                    progress: { progress in
                        continuation.yield(.progress(progress))
                    },
                    completion: { result in
                        switch result {
                        case .success(let data):
                            continuation.yield(.completed(data))
                            continuation.finish()
                        case .failure(let error):
                            continuation.finish(throwing: error)
                        }
                    }
                )
            } catch {
                continuation.finish(throwing: error)
            }
        }
    }
}

// 消费数据流
do {
    for try await status in downloader.downloadStream(for: url) {
        switch status {
        case .progress(let value):
            print("下载进度:(value * 100)%")
        case .completed(let data):
            print("完成下载,数据大小:(data.count)字节")
        }
    }
} catch {
    print("下载失败:(error)")
}

核心优势

  1. 统一处理进度和完成事件
  2. 支持for await循环实时消费数据
  3. 错误自动在循环外捕获

四、线程安全:从GCD到Actor的迁移

传统队列方案

class UserDatabase {
    private var storage = [String: User]()
    private let queue = DispatchQueue(label: "user.db.queue")
    
    func store(_ user: User) {
        queue.async { self.storage[user.id] = user }
    }
    
    func load(id: String) -> User? {
        queue.sync { storage[id] }
    }
}

Actor安全实现

actor UserDatabase {
    private var storage = [String: User]()
    
    func store(_ user: User) {
        storage[user.id] = user
    }
    
    func load(id: String) -> User? {
        storage[id]
    }
}

// 安全调用
let db = UserDatabase()
await db.store(user)
let loadedUser = await db.load(id: "123")

重大改进

  1. 编译器保证线程安全
  2. 消除手动队列管理
  3. 支持await挂起语义

五、桥接旧代码:CheckedContinuation妙用

闭包接口现代化改造

// 传统回调方法
func fetchMessages(completion: @escaping ([Message]) -> Void) {
    URLSession.shared.dataTask(with: messagesURL) { data, _, _ in
        let messages = (try? JSONDecoder().decode([Message].self, from: data ?? Data())) ?? []
        completion(messages)
    }.resume()
}

// 转换为async接口
func fetchMessages() async -> [Message] {
    await withCheckedContinuation { continuation in
        fetchMessages { messages in
            continuation.resume(returning: messages)
        }
    }
}

// 新调用方式
let messages = await fetchMessages()
print("获取到(messages.count)条消息")

关键步骤

  1. 使用withCheckedContinuation包装旧接口
  2. 在回调中恢复continuation
  3. 保持原有错误处理逻辑

六、主线程安全:@MainActor实践

UI更新最佳实践

@MainActor
func updateUI(with messages: [Message]) {
    tableView.reloadData()
    unreadBadge.text = "(messages.count)"
}

// 自动调度到主线程
Task {
    let messages = await fetchMessages()
    await updateUI(with: messages)
}

CLLocationManager桥接

@MainActor
class LocationService: NSObject, CLLocationManagerDelegate {
    private var continuation: CheckedContinuation<CLLocation, Error>?
    private let manager = CLLocationManager()
    
    override init() {
        super.init()
        manager.delegate = self
    }
    
    func currentLocation() async throws -> CLLocation {
        try await withCheckedThrowingContinuation { continuation in
            self.continuation = continuation
            manager.requestLocation()
        }
    }
    
    // Delegate实现
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        continuation?.resume(returning: locations)
        continuation = nil
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        continuation?.resume(throwing: error)
        continuation = nil
    }
}

迁移收益总结

通过本文的改造方案,您的代码将获得:

  1. 可读性提升:消除嵌套闭包,代码执行流清晰可见
  2. 性能优化:Task Group实现真正的并行计算
  3. 安全性增强:Actor机制杜绝数据竞争
  4. 维护成本降低:统一错误处理,减少回调管理
  5. 现代API兼容:无缝接入SwiftUI、Combine等新框架

建议采用渐进式迁移策略,优先改造核心业务模块,逐步享受Swift并发编程带来的开发红利。