一、核心知识点罗列
(一)Async/Await:现代异步编程范式
- 核心定义与语法规则
- 异步函数声明:用
async标记函数,支持throws抛出错误(如func fetchData() async throws -> Data),调用时需在async上下文用await标记(如let data = try await fetchData())。 - 语法特性:代码线性执行,无需嵌套回调;错误处理与同步代码一致,支持
try/catch捕获异步错误。
- 异步函数声明:用
- 执行机制
- 非阻塞暂停:
await会暂停当前任务,释放线程资源,待异步操作完成后恢复执行,线程可处理其他任务。 - 上下文保存:暂停时自动保存函数调用栈、变量状态,恢复时无缝续行,对开发者透明。
- 运行时调度:异步函数由Swift运行时动态分配线程,无需手动管理线程池。
- 非阻塞暂停:
(二)与Completion Handler的对接
- 核心需求:兼容现有回调式异步API,通过“续体(Continuation)”封装为Async/Await风格。
- 关键API:withCheckedThrowingContinuation
- 作用:将回调逻辑转换为异步函数,
withCheckedThrowingContinuation支持抛出错误,withCheckedContinuation不支持。 - 强制约束:必须调用且仅调用一次
continuation.resume()(返回值或抛出错误),否则会导致内存泄漏或任务卡死。 - 示例:封装URLSession数据请求为异步函数(文档中核心示例场景)。
- 作用:将回调逻辑转换为异步函数,
(三)结构化并发(Structured Concurrency)
- 核心理念:通过“父-子任务层级关系”管理并发,父任务等待所有子任务完成后才结束,自动传播取消信号,避免孤儿任务。
- 任务(Task):并发执行基本单元
- 创建方式:
Task { await doSomething() },闭包需标记async。 - 结果获取:通过
task.result(阻塞线程)或await task.value(异步获取)。 - 状态管理:支持手动取消(
task.cancel()),取消信号自动传播给子任务。
- 创建方式:
- async let:固定数量并行子任务
- 语法:
async let data1 = fetchData(with: url1),并行创建多个子任务,需通过await统一获取结果。 - 特性:任一子任务抛出错误,整体立即失败(快速失败机制)。
- 语法:
- 任务组(Task Group):动态管理子任务
- 适用场景:动态创建子任务(如遍历数组生成任务),支持聚合结果、处理部分失败。
- 语法:
withTaskGroup(of: ResultType.self) { group in ... },通过group.addTask添加子任务。 - 核心能力:遍历子任务结果,灵活处理成功/失败案例(如收集所有成功数据,忽略失败任务)。
(四)Sendable类型与函数
- 核心定义:
Sendable协议标记类型可安全跨任务传递(跨线程访问),保证线程安全。 - 自动遵守场景
- 值类型(结构体、枚举):所有存储属性遵守
Sendable时自动遵守。 - 不可变引用类型:
final class且所有存储属性为let并遵守Sendable。 - 标准库类型:
Int、String、Array等默认遵守。
- 值类型(结构体、枚举):所有存储属性遵守
- 手动遵守与限制
- 手动声明:
extension 类型: Sendable {},需确保类型内部线程安全(如用锁保护可变状态)。 - 强制标记:非Sendable类型需通过
@unchecked Sendable强制标记(谨慎使用,跳过编译检查)。 - 约束要求:异步函数的参数、返回值,以及
Task闭包捕获的变量,必须遵守Sendable(否则编译报错)。
- 手动声明:
(五)取消机制(Cancellation)
- 核心模型:协作式取消
- 本质:任务收到取消信号后需自行检查并响应,不会被强制终止,需开发者主动配合。
- 信号传播:父任务取消→自动传播给所有子任务;子任务取消→不影响父任务(除非抛出
CancellationError)。
- 取消触发方式
- 手动取消:调用
task.cancel()。 - 结构化取消:父任务结束前未完成,自动取消子任务。
- 超时取消:通过
Task.withTaskCancellationHandler或Task.sleep(nanoseconds:)实现。
- 手动取消:调用
- 取消响应方式
- 检查状态:
Task.isCancelled(返回布尔值)、Task.checkCancellation()(收到信号抛出CancellationError)。 - 资源清理:通过
defer语句确保取消后释放资源(如关闭文件句柄、取消网络请求)。
- 检查状态:
(六)非结构化并发(Unstructured Concurrency)
- 核心定义:不遵循“父-子任务层级”,任务独立执行,需手动管理生命周期(取消、结果获取),风险高于结构化并发。
- 创建方式:
Task.detached(priority:operation:),创建独立任务,无父任务关联。 - 关键注意事项
- 必须手动处理取消信号,避免任务泄露。
- 捕获的变量需为
Sendable,或通过@unchecked Sendable强制标记。 - 适用场景:长期运行的后台任务(如监听网络状态)、与非Swift并发模型交互(如Objective-C线程)。
(七)Actor:并发安全的引用类型
- 核心目标:解决共享状态的线程安全问题,通过“串行执行”保证同一时间仅一个任务访问Actor的属性和方法。
- 核心特性
- 隔离状态:Actor的存储属性仅能在内部或通过其方法访问,外部不可直接修改。
- 异步访问:外部访问Actor的属性/方法需用
await(Actor需调度任务串行执行)。 - 可重入性:支持嵌套调用(如Actor A调用Actor B的方法,不会阻塞自身),仍保证串行执行。
- 特殊Actor:Main Actor
- 绑定主线程:用于UI更新等必须在主线程执行的操作,通过
@MainActor标记函数或类型。 - 跨上下文调用:从非Main Actor上下文调用
@MainActor函数需用await,确保主线程执行。
- 绑定主线程:用于UI更新等必须在主线程执行的操作,通过
- 性能特点:串行执行带来少量调度开销,但避免手动加锁和资源竞争,整体性能优于手动加锁的类。
(八)执行上下文推断
- 核心规则:Swift根据函数/类型的Actor标记自动推断执行线程,无需手动指定。
@MainActor标记:默认在主线程执行。- Actor实例方法:在该Actor的串行队列执行。
- 未标记异步函数:由运行时调度至任意可用线程。
- 跨上下文调用规则
- 非Main Actor → Main Actor:需
await异步调用。 - Main Actor → 普通异步函数:运行时自动调度,无需额外处理。
- 非Main Actor → Main Actor:需
二、重点知识点总结
(一)Async/Await的核心价值:简化异步编程
- 替代回调嵌套:将“回调地狱”转为线性代码,可读性与可维护性大幅提升。
- 统一错误处理:异步错误处理与同步代码一致(
try/catch),无需在回调中单独处理错误。 - 兼容现有API:通过续体(Continuation)轻松封装回调式API,平滑迁移至现代并发模型。
(二)结构化并发的核心优势:安全管理任务生命周期
- 自动等待子任务:父任务必须等待所有子任务完成,避免孤儿任务和资源泄漏。
- 取消信号自动传播:父任务取消时,所有子任务同步收到信号,便于统一清理资源。
- 灵活任务创建:
async let适配固定数量并行任务,Task Group适配动态数量任务,覆盖绝大多数并发场景。
(三)Sendable协议:并发安全的类型保障
- 编译时检查:通过
Sendable约束,确保跨任务传递的类型线程安全,避免运行时数据竞争。 - 自动与手动遵守:值类型默认遵守,引用类型需手动声明并保证内部安全,谨慎使用
@unchecked Sendable。
(四)Actor:并发安全的共享状态解决方案
- 串行执行机制:从根本上避免资源竞争,无需手动加锁,简化线程安全代码编写。
- Main Actor的特殊作用:专门用于UI操作,通过
@MainActor标记确保UI更新在主线程执行。 - 与类的选择:并发场景下需共享可变状态→优先用Actor;无需并发访问→用类或结构体。
(五)协作式取消的实践原则
- 主动响应取消:任务需定期检查
Task.isCancelled或调用Task.checkCancellation(),收到信号后及时清理资源并退出。 - 避免强制终止:协作式取消不会强制终止任务,需开发者主动配合,确保任务优雅退出。
三、难点知识点总结
(一)续体(Continuation)的正确使用
- 核心陷阱:封装回调式API时,必须调用且仅调用一次
continuation.resume(),遗漏会导致任务卡死,多次调用会崩溃。 - 常见错误场景:
- 回调分支未全覆盖(如网络请求的
data、error均为nil时未处理),导致continuation未调用。 - 异步操作失败后,既调用
resume(throwing:),又在后续代码调用resume,导致多次触发。
- 回调分支未全覆盖(如网络请求的
- 解决方案:用
defer确保resume调用,或通过guard语句覆盖所有分支。
(二)结构化与非结构化并发的选择
- 难点:区分两种模型的适用场景,过度使用非结构化并发易导致任务泄漏、取消管理混乱。
- 选择原则:
- 优先结构化并发(
async let、Task Group):适用于大多数场景,安全可控。 - 仅必要时使用非结构化并发(
Task.detached):如长期后台任务,需手动管理取消和生命周期。
- 优先结构化并发(
(三)Actor的隔离规则与跨Actor调用
- 隔离陷阱:Actor的属性仅能内部访问,外部需通过
await调用方法,容易误写为直接访问导致编译报错。 - 跨Actor调用风险:多个Actor嵌套调用可能导致死锁(如Actor A调用Actor B,同时Actor B调用Actor A)。
- 解决方案:避免Actor间循环依赖,设计非阻塞API,利用Actor的可重入性优化嵌套调用逻辑。
(四)取消信号的传播与响应
- 难点:取消信号的传播路径需清晰,子任务未响应取消会导致父任务等待超时,资源无法释放。
- 响应关键:
- 计算密集型任务:循环中定期检查
Task.isCancelled,收到信号立即退出。 - I/O密集型任务:收到信号后取消底层操作(如网络请求、文件读写),再通过
defer清理资源。
- 计算密集型任务:循环中定期检查
(五)Main Actor与UI线程安全
- 常见误区:
- 误在非Main Actor上下文操作UI,导致UI线程安全问题。
- 过度标记
@MainActor,导致所有代码在主线程执行,降低并发效率。
- 规避方案:
- 仅UI相关操作标记
@MainActor,业务逻辑和I/O操作不标记。 - 跨上下文调用UI方法时,通过
await确保主线程执行,避免手动切换线程。
- 仅UI相关操作标记
四、总结
本章核心围绕Swift现代并发模型展开,核心逻辑是“通过Async/Await简化异步编程,通过结构化并发保障任务安全,通过Sendable和Actor确保线程安全”。重点在于掌握Async/Await的语法与回调API封装、结构化并发的任务管理、Actor的并发安全保障及Main Actor的UI适配;难点集中在续体的正确使用、取消信号的响应、Actor的隔离规则与跨调用风险,以及结构化与非结构化并发的场景区分。
实际开发中,应遵循“优先结构化并发、优先Actor保障安全、主动响应取消”的原则:
- 异步API优先采用Async/Await风格,通过续体封装现有回调API。
- 并发任务优先使用
async let或Task Group,避免非结构化并发的风险。 - 共享可变状态优先用Actor,而非手动加锁的类,从根本上避免资源竞争。
- UI操作必须在Main Actor上下文执行,通过
@MainActor标记确保线程安全。
通过掌握这些知识点,可写出安全、高效、易维护的并发代码,充分利用多核处理器性能,同时规避传统并发模型的常见问题(如回调地狱、资源竞争、内存泄漏)。
如果需要,我可以帮你整理并发核心API对比表,或针对某个难点(如续体封装、Task Group动态任务管理、Actor死锁规避)提供详细代码示例。当前文件内容过长,豆包只阅读了前 9%。