Swift多线程方案-Concurrency

17 阅读3分钟

简介

Swift Concurrency(async/await)是从 Swift 5.5 开始引入的一套并发编程模型,用来替代传统的回调(callback)、闭包嵌套(callback hell)、以及部分 GCD 使用场景,让异步代码写起来像同步代码一样清晰。

async / await

  • async:标记函数是 异步函数
  • await:表示 等待异步结果

本质:await 会“挂起当前任务”,但不会阻塞线程

示例

func fetchUser() async -> String {
    return "Tom"
}

func loadData() async {
    let user = await fetchUser()
    print(user)
}

async throws

支持错误处理(替代 callback 的 error)

enum NetworkError: Error {
    case failed
}

func fetchData() async throws -> String {
    throw NetworkError.failed
}

func load() async {
    do {
        let result = try await fetchData()
        print(result)
    } catch {
        print("error: \(error)")
    }
}

Task

Task 是并发执行的基本单位,类似 GCD 的 block,也是一个对象。

  • 普通 Task
Task {
    let data = await fetchUser()
    print(data)
}
let task = Task {
    ...
}
  • Detached Task(独立线程)
Task.detached {
    await doSomething()
}

区别:

Task:继承当前 Actor / 优先级 / 上下文

detached:完全独立(慎用)

async let

并发执行

场景:多个接口同时请求

func loadData() async {
    async let user = fetchUser()
    async let posts = fetchPosts()
    
    let result = await (user, posts)
    print(result)
}

TaskGroup

任务组

场景:批量请求 / 并发处理列表

func fetchAll() async {
    // 每个子任务返回值类型是 String
    await withTaskGroup(of: String.self) { group in
        // 动态创建多个异步任务 并发执行,逐个获取结果
        for i in 1...3 {
            group.addTask {
                return "Task \(i)"
            }
        }
        
        for await result in group {
            print(result)  // 打印每个任务的结果;顺序不确定-先完成先打印
        }
    }
}

生命周期:当代码执行完withTaskGroup { ... },会自动等待所有子任务完成,然后自动释放资源。另外,也能自动取消未完成任务(如果提前退出)。任一任务抛错 就会全部取消。

手动取消 group.cancelAll() ,如 退出页面

使用场景:

  • 动态任务数量 (对比 async let - 任务数固定)
  • 适合列表/批量处理

1、批量接口请求

func fetchUsers(ids: [Int]) async -> [User] {
    var result: [User] = []
    
    await withTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                return await fetchUser(id: id)
            }
        }
        
        for await user in group {
            result.append(user)
        }
    }
    
    return result
}

2、批量下载

func downloadImages(urls: [URL]) async -> [UIImage] {
    var images: [UIImage] = []
    
    await withTaskGroup(of: UIImage?.self) { group in
        for url in urls {
            group.addTask {
                return try? await downloadImage(url: url)
            }
        }
        
        for await img in group {
            if let img = img {
                images.append(img)
            }
        }
    }
    
    return images
}

3、并发计算任务(CPU)

func processData(items: [Int]) async {
    await withTaskGroup(of: Void.self) { group in
        for item in items {
            group.addTask {
                heavyWork(item)
            }
        }
    }
}

4、限制最大并发数(手动处理)

func fetchWithLimit(ids: [Int]) async {
    let maxConcurrent = 2
    await withTaskGroup(of: String.self) { group in
        var iterator = ids.makeIterator()
        // 先启动前 N 个任务
        for _ in 0..<maxConcurrent {
            if let id = iterator.next() {
                group.addTask {
                    return await fetchUser(id: id)
                }
            }
        }
        // 每完成一个,就补一个
        for await result in group {
            print(result)
            if let nextId = iterator.next() {
                group.addTask {
                    return await fetchUser(id: nextId)
                }
            }
        }
    }
}

Actor

线程安全方案

用于解决数据竞争问题(替代锁)

actor Counter {
    private var value = 0
    
    func increment() {
        value += 1
    }
    
    func getValue() -> Int {
        return value
    }
}
let counter = Counter()

Task {
    await counter.increment()
    let v = await counter.getValue()
    print(v)
}

Actor = 自动串行队列 + 数据隔离

对比

方案问题
NSLock易死锁
DispatchQueue需要手动管理
Actor天然安全

实战应用场景

  • 网络请求
func fetchUser() async -> User { ... }
func fetchPosts() async -> [Post] { ... }

func load() async {
    let user = await fetchUser()
    let posts = await fetchPosts()
}

——对比旧方案

fetchUser { result in
    fetchPosts { posts in
        // 嵌套地狱
    }
}
  • 多个接口并发请求

多个请求任务并行执行,等待异步结果

func loadPage() async {
    async let banner = fetchBanner()
    async let list = fetchList()
    async let profile = fetchProfile()
    
    let (b, l, p) = await (banner, list, profile)
}
  • 主线程更新UI
func loadData() {
    Task {
        let data = await fetchData()
        
        await MainActor.run {
            self.label.text = data
        }
    }
}

或 使用@MainActor

@MainActor
func updateUI() {
    label.text = "Hello"
}
  • 取消任务
let task = Task {
    let data = await fetchData()
}

task.cancel()
  • 图片加载
func downloadImage(url: URL) async throws -> UIImage {
    let (data, _) = try await URLSession.shared.data(from: url)
    return UIImage(data: data)!
}
let task = Task {
    let image = try await downloadImage(url: url)
    cell.imageView.image = image
}

注意:需要处理 cell 复用问题(取消任务)-》 避免图片错位

override func prepareForReuse() {
    task?.cancel()
}
  • 顺序依赖
func process() async {
    let token = await login()
    let data = await fetchData(token: token)
    let result = await parse(data)
}

对比

方案特点
GCD底层强,但难维护
Operation可控但复杂
async/await简洁 + 可读性强

本质: • GCD:你管理线程 • async/await:系统帮你调度

总结

Swift Concurrency 本质就是:

用“同步写法”写“异步代码”,并且保证线程安全