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 会不会乱序? 内部用索引对还原,保证与输入顺序一致
相关推荐
初级代码游戏1 天前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏1 天前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui2 天前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift
zhangfeng11332 天前
CSDN星图 支持大模型微调 trl axolotl Unsloth 趋动云 LLaMA-Factory Unsloth ms-swift 模型训练
服务器·人工智能·swift
zhyongrui3 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
大熊猫侯佩3 天前
Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学
swift·observable·actor·concurrency·sendable·nonsendable·data race
2501_915921435 天前
在没有源码的前提下,怎么对 Swift 做混淆,IPA 混淆
android·开发语言·ios·小程序·uni-app·iphone·swift
00后程序员张5 天前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
大熊猫侯佩6 天前
星际穿越:SwiftUI 如何让 ForEach 遍历异构数据(Heterogeneous)集合
swiftui·swift·遍历·foreach·any·异构集合·heterogeneous
hjs_deeplearning6 天前
认知篇#15:ms-swift微调中gradient_accumulation_steps和warmup_ratio等参数的意义与设置
开发语言·人工智能·机器学习·swift·vlm