Swift 函数完全指南(四):从 `@escaping` 到 `async/await`——打通“回调→异步→并发”任督二脉

历史包袱:海量第三方 SDK 仍是回调形态

swift 复制代码
// 某社交 SDK,2025 年依旧没变
func login(
    _ platform: String,
    completion: @escaping (Result<User, Error>) -> Void
)

痛点:

  • 嵌套地狱(Callback Pyramid)
  • 错误处理分散
  • 难以取消

官方桥接器:withCheckedThrowingContinuation

核心思想

把"回调"转成"Continuation"(续体),让编译器帮你生成 async 上下文。

1 行封装

swift 复制代码
extension SocialSDK {
    static func login(_ platform: String) async throws -> User {
        try await withCheckedThrowingContinuation { continuation in
            login(platform) { result in
                continuation.resume(with: result)
            }
        }
    }
}

调用方瞬间清爽:

swift 复制代码
let user = try await SocialSDK.login("WeChat")

取消传播

swift 复制代码
func loginCancelable(_ platform: String) async throws -> User {
    try await withTaskCancellationHandler(
        operation: {
            try await withCheckedThrowingContinuation { con in
                let task = SocialSDK.login(platform) { con.resume(with: $0) }
                // 注册取消回调
                con.onTermination = { _ in task.cancel() }
            }
        },
        onCancel: { SocialSDK.cancelLogin(platform) }
    )
}

并发安全:让"函数"跨线程也无锁

  1. 数据竞争的本质
swift 复制代码
var counter = 0
DispatchQueue.concurrentPerform(iterations: 1_000_000) { _ in
    counter += 1   // 未保护 → 结果 < 1_000_000
}
  1. actor 把"状态"封装成"函数"
swift 复制代码
actor Counter1 {
    private var value = 0
    func increment() -> Int {
        value += 1
        return value
    }
}

Task {
    let counter = Counter1()
    await withTaskGroup(of: Void.self) { group in
        for _ in 0..<1_000_000 {
            group.addTask { _ = await counter.increment() }
        }
    }
    print(await counter.increment()) // 1_000_001
}
  • actor 内部任意函数都是"串行队列"语义,外部调用需 await
  • 编译期保证无锁线程安全。
  1. 纯函数 + Sendable 实现"无锁并行"
swift 复制代码
/// 纯函数:无副作用,仅依赖参数
func hash(_ x: Int) -> Int { x &* 31 &+ 7 }

Task.detached {
    /// Int 已遵循 Sendable,可安全跨线程
    await withTaskGroup(of: Int.self) { group in
        for i in 0..<1_000_000 {
            group.addTask { hash(i) }
        }
        let hashes = await group.reduce(0, +)
    }
}

规则:

  • 值类型(IntStringArray 等)默认 Sendable
  • 自定义引用类型需显式 final class Box: Sendable 并保证内部不可变或加锁。

函数式高阶算子:并发版 map / filter / reduce

  1. concurrentMap
swift 复制代码
extension Sequence {
    func concurrentMap<T: Sendable>(
        _ transform: @Sendable @escaping (Element) async throws -> T
    ) async rethrows -> [T] where Element : Sendable {
        try await withThrowingTaskGroup(of: (Int, T).self) { group in
            // 1. 提交所有任务
            for (index, item) in self.enumerated() {
                group.addTask { (index, try await transform(item)) }
            }
            // 2. 收集结果,保持顺序
            var pairs = [(Int, T)]()
            for try await pair in group { pairs.append(pair) }
            return pairs.sorted { $0.0 < $1.0 }.map(\.1)
        }
    }
}
  1. 使用:并发下载 100 张缩略图
swift 复制代码
Task {
    let urls = (0..<100).map { "https://picsum.photos/200/300?random=\($0)" }
    let _: [Data] = try await urls.concurrentMap { url -> Data in
        let (data, _) = try await URLSession.shared.data(from: URL(string: url)!)
        return data
    }
}
  • 自动利用所有 CPU 核心
  • 顺序与输入一致,无需二次排序
  1. concurrentCompactMap / concurrentFilter 同理,留给读者练习。

终极案例:用"函数"写出可组合的并发管道

需求:

  1. 并发下载 HTML →
  2. 并发解析标题 →
  3. 顺序汇总报告
swift 复制代码
struct Report {
    let url: String
    let title: String
}

func pipeline(_ urls: [String]) async throws -> [Report] {
    try await urls
        .concurrentMap { url -> (String, Data) in
            let (data, _) = try await URLSession.shared.data(from: URL(string: url)!)
            return (url, data)
        }
        .concurrentMap { (url, data) -> Report in
            let html = String(decoding: data, as: UTF8.self)
            let title = html.replacingOccurrences(
                of: #"<title>(.*?)</title>"#,
                with: "$1",
                options: .regularExpression
            )
            return Report(url: url, title: title.trimmingCharacters(in: .whitespacesAndNewlines))
        }
}

特点:

  • 阶段之间用临时元组传递上下文,避免全局可变状态。
  • 任意阶段加 try 即可自动终止后续任务,异常传播靠 rethrows 机制。

思维导图(文字版)

swift 复制代码
回调 → withContinuation → async/await
actor / Sendable → 无锁并发
高阶函数 → concurrentMap → 函数式并行管道

面试速答卡片

问题 一句话答案
旧 SDK 回调如何转 async withCheckedThrowingContinuation 一键桥接
actor 与类最大区别? 编译期强制串行访问,自动解决数据竞争
Sendable 对函数意味着什么? 闭包及其捕获变量必须线程安全,否则编译报错
concurrentMap 会不会乱序? 内部用索引对还原,保证与输入顺序一致
相关推荐
HarderCoder6 小时前
Swift 函数完全指南(三):`@autoclosure`、`rethrows`、`@escaping` 与内存管理
swift
HarderCoder6 小时前
Swift 函数完全指南(二):泛型函数与可变参数、函数重载、递归、以及函数式编程思想
swift
HarderCoder6 小时前
Swift 函数完全指南(一)——从入门到嵌套
swift
jh_cao15 小时前
(4)SwiftUI 基础(第四篇)
ios·swiftui·swift
progalchemist1 天前
Quick SwiftObjective-C测试框架入门教程
开发语言·其他·objective-c·swift
HarderCoder1 天前
Swift 闭包(Closure)从入门到深入:语法、捕获与实战
swift
HarderCoder1 天前
Swift 集合类型详解(三):自定义集合、持久化结构与 ORM 共舞
swift
HarderCoder1 天前
Swift 集合类型详解(一):Array、Set、Dictionary 全貌与选型思路
swift
HarderCoder2 天前
Swift 集合类型详解(二):自定义 Hashable、值语义与性能陷阱
swift