View in English

  • 打开菜单 关闭菜单
  • Apple Developer
搜索
关闭搜索
  • Apple Developer
  • 新闻
  • 探索
  • 设计
  • 开发
  • 分发
  • 支持
  • 账户
在“”范围内搜索。

快捷链接

5 快捷链接

视频

打开菜单 关闭菜单
  • 专题
  • 相关主题
  • 所有视频
  • 关于

更多视频

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 采用 Swift 并发

    与我们一起了解 Swift 并发方面的核心概念。并发可帮助你提高 App 的响应速度和性能,而 Swift 旨在让正确编写异步和并发代码变得更加简单。我们会介绍将 App 编程从单线程转换为并发流程所需的步骤。无论你想让代码更加异步化、将代码移到后台,还是在并发任务中共享数据,我们都将帮助你确定充分利用 Swift 并发功能的恰当方式和时机。

    章节

    • 0:00 - 简介
    • 3:17 - 单线程代码
    • 6:00 - 异步任务
    • 7:24 - 交错执行
    • 10:22 - 并发简介
    • 11:07 - 并发函数
    • 13:10 - 非限定代码
    • 14:13 - 并发线程池
    • 14:58 - 共享数据
    • 15:49 - 值类型
    • 17:16 - Actor 限定类型
    • 18:30 - 类
    • 23:18 - Actor
    • 26:12 - 总结

    资源

    • Swift Migration Guide
    • The Swift Programming Language: Concurrency
      • 高清视频
      • 标清视频

    相关视频

    WWDC25

    • 跟着视频学编程:使用 Swift 并发机制提升 App 性能

    WWDC23

    • 超越结构化并发的基础
  • 搜索此视频…

    大家好! 我是 Swift 团队的 Doug 很高兴 能和大家探讨如何在 App 中 充分发挥 Swift 并发功能的优势 并发编程能让代码同时执行多个任务 比如当 App 需要从 磁盘读取文件或发起网络请求 等待数据返回时 使用并发可以显著提升响应速度 同样在处理大型图像等任务时 也能通过并发 将繁重运算转移到后台执行 Swift 的并发模型经过精心设计 让并发代码更容易正确编写 它通过显式标记并发上下文 明确标识跨任务共享的数据 并利用这些信息在编译阶段 就能检测潜在的数据争用问题 因此你可以按需引入并发机制 而无需担心产生难以修复的数据争用

    大多 App 只需在特定场景使用并发 有些甚至完全不需要 由于并发代码比单线程代码更复杂 建议只在需要时引入并发

    App 初始阶段完全可以 在主线程运行所有代码 事实上很多功能 仅靠单线程就能完美实现 主线程是处理 UI 事件 和更新用户界面的核心线程 如果你的 App 没有大量计算需求 保持单线程架构是 完全可行的方案! 最终 你可能需要引入异步代码 比如从网络获取内容时 你的代码可以等待网络传输内容 而不会导致用户界面卡顿 如果这些任务运行时间过长 我们可以将它们转移到后台线程 与主线程并行处理

    当 App 继续演进时 可能会发现 将所有数据都放在主线程处理 会导致性能下降 这时 就可以针对特定场景引入 专为后台运行设计的数据类型

    Swift 并发编程提供了 Actor 和 Task 等工具 专门用于实现这类并发操作 大型 App 的架构最终 可能会演变成这样 但并非从一开始就要构建复杂架构 也不是所有 App 都需要走到这一步 本次讲座将逐步演示 如何将 App 从单线程 演进到并发架构 在每个演进阶段 我们会帮助你判断时机、 讲解对应的 Swift 语言特性、 演示高效使用方法 并解析底层工作原理 首先解析单线程代码 在 Swift 并发模型中的运行机制 然后引入异步任务处理高延迟操作 如网络访问 接着通过并发机制 将任务移至后台线程 并学习如何在不引入数据争用 的情况下跨线程共享数据 最后 我们将使用 Actor 将数据完全移出主线程 现在让我们从单线程代码开始 运行程序时 代码开始在主线程运行 除非你显式引入并发机制 在其他地方运行代码 否则新增代码会始终在主线程执行 单线程代码的编写和维护更为简单 因为同一时间只会执行一个操作 即便后续引入并发机制 Swift 也会为主线程代码提供保护

    主线程及它的所有数据 都由主 Actor 表示 由于主线程只有一个 主 Actor 内部不存在并发问题 可以使用 @MainActor 标注来指定 数据或代码必须在主 Actor 上运行 Swift 会确保主 Actor 代码 仅在主线程执行 并且主 Actor 数据 只能在主线程被访问 这类代码被称为隔离于主 Actor Swift 默认使用主 Actor 来保护主线程代码 这就像 Swift 编译器自动 为模块中的所有代码 都加上了 @MainActor 因此 我们可以像访问静态变量一样 自由地在任何地方 访问共享状态 在主 Actor 模式下 只要不主动引入并发 就无需考虑并发访问的问题 默认使用主 Actor 保护代码的 功能是由构建设置驱动的 这主要适用于你的主 App 模块 以及所有专注于 UI 交互的模块 在 Xcode 26 创建的新 App 项目中 这一模式默认启用 在本讲座中 我们将假设 所有代码示例 都启用了主 Actor 模式

    现在让我们为图像模型添加一个方法 用于从 URL 获取并显示图像 我们需要从本地文件载入图像 然后解码 并在 UI 中显示 我们的 App 完全不涉及并发处理 所有工作都由单一的主线程完成 整个函数在主线程上连续运行 只要其中每个操作都 足够快速就不会有问题

    目前 我们仅支持从本地读取文件 若要让 App 能够通过网络获取图像 我们就需要使用不同的 API

    这个 URLSession API 可以根据 给定的 URL 从网络获取数据 但如果在主线程上运行这个方法 在数据下载完成前 用户界面都会处于卡顿状态 作为开发人员 保持 App 的 响应能力至关重要 这意味着要避免长时间占用主线程 导致 UI 出现卡顿或假死现象 Swift 并发机制提供了解决方案: 在等待网络请求等数据时 可以使用异步任务 来避免阻塞主线程 为了防止这类卡顿问题 网络访问本就是异步执行的 我们可以将 fetchAndDisplayImage 方法改造为支持异步调用 只需将函数标记为‘async’并使用 ‘await’调用 URL session API await 标明了函数 可能被挂起的位置 这意味着在当前线程上停止执行 直到等待的事件完成 函数才能恢复运行

    我们可以理解为 将函数拆分为两部分: 第一部分执行到开始获取图像为止 第二部分则在图像获取完成后 继续执行 通过这种方式拆分函数 我们允许其他任务 在这两部分之间运行 从而保持 UI 的响应性

    实际上 许多库 API 如 URLSession 会将任务分配到后台执行 我们仍未在代码中 引入并发 因为根本不需要! 我们只是通过将部分代码设为异步 并调用能自动 分配任务的库 API 就提升了应用程序的响应性能 在代码层面 我们唯一需要做的 就是采用 async/await 语法

    到目前为止 我们的代码 仅运行单个异步函数 每个异步函数都在一个任务中执行 任务是独立于其他代码的执行单元 应当被设计为完成 从开始到结束的特定操作 通常我们会基于按钮点击 等事件来创建任务 在这里 任务负责完成完整的 “获取-显示图像”操作 一个 App 可以包含多个异步任务 除了我们讨论的 “获取-显示图像”任务外 这里我还添加了第二个任务 即先获取新闻数据并显示 然后等待刷新操作 每个任务都会按顺序 从头到尾执行它的操作流程 虽然数据获取在后台进行 但每个任务中的其他操作 都会在主线程运行 且同一时间只能执行一个操作 由于任务之间相互独立 它们可以轮流占用主线程资源 主线程会根据任务就绪状态 依次执行各个任务的对应操作片段 这种让单个线程在多个任务间 交替执行的机制称为“交错” 它通过最大化利用系统资源 来提升整体性能 线程可以尽快开始处理任意任务 避免在等待单个操作时 处于闲置状态 若图像获取比新闻获取先完成 主线程就会优先执行图像解码与显示 再显示新闻 反之若新闻数据先到位 主线程则会先展示新闻 再解码图像

    当你的 App 需要同时执行 多个独立操作时 并行异步任务会非常高效 当操作需要严格按特定顺序执行时 则应将这些操作放在同一个任务中

    对于网络请求等高延迟操作 使用异步任务能隐藏等待时间 从而确保 App 始终保持响应性 在这方面 一些库能提供帮助 它们通过异步 API 代表你处理并发操作 而你自己的代码 仍可保持在主线程运行 URLSession API 已经在 后台线程处理网络请求 为我们引入了并发机制 而我们的“获取-显示图像” 操作仍运行在主线程 我们可能会发现图像解码耗时过长 比如处理大图时出现 UI 卡顿

    单线程异步模式 通常已能满足 App 需求 但当主线程负载过重 导致 App 没有响应时 就需要借助 Instruments 等性能分析工具定位耗时瓶颈 若某项工作无需引入并发即可优化 则应优先采用单线程方案 当性能已达瓶颈时 才需要考虑引入并发机制 并发允许代码在后台线程 与主线程并行执行 既能避免阻塞 UI 又能通过利用系统多核 CPU 提升处理效率 我们的目标是将解码任务移出主线程 转由后台线程处理 由于当前处于主 Actor 默认模式 fetchAndDisplayImage 和 decodeImage 都被隔离在主 Actor 中 主 Actor 代码可以自由访问 仅限主线程的所有数据和代码 这种安全性正是源于 主线程不存在并发冲突

    我们可以通过给 decodeImage 函数 添加 @concurrent 属性 来将它移至后台执行 @concurrent 会指示 Swift 在后台线程运行这个函数 但改变执行位置的同时 也改变了我们对 decodeImage 所能访问的状态的假设 让我们看看具体实现 当前实现会检查 存储在主 Actor 中的 图像缓存字典 这种操作 本应仅在主线程安全执行 Swift 编译器会精准指出 函数试图访问 主 Actor 数据的代码位置 这正是我们在引入并发时 避免引入错误所需的关键信息 要安全地引入并发机制 有几种策略可以解除 与主 Actor 的绑定关系 在某些情况下 可以将 主 Actor 代码移至始终在 主 Actor 上运行的调用方中 若需要确保操作同步执行 这是种理想的策略 或者可以通过 await 从并发代码中 异步访问主 Actor

    若代码完全无需依赖主 Actor 则可添加 nonisolated 关键字 将它与任何 Actor 分开 现在我们先探讨第一种策略 其他方法后面再讨论 我打算将图像缓存逻辑移至 运行在主 Actor 的 fetchAndDisplayImage 中 在发起异步调用前检查缓存 能有效消除延迟 若图像已缓存 fetchAndDisplayImage 将完全同步执行且不会挂起 这意味着结果可立即交付至 UI 仅当图像未缓存时才会触发挂起

    此外 由于不再需要 我们可以移除 decodeImage 中的 url 参数 现在 我们只需等待 decodeImage 的结果即可

    @concurrent 函数总是会 从 Actor 中切换出来运行 若希望函数保持在 调用方的 Actor 上运行 可以使用 nonisolated 关键字 Swift 还提供了更多引入并发的方式 详见“超越结构化并发的基础”

    如果我们要将解码 API 作为供多客户端使用的库函数 采用 @concurrent 并非总是 最佳的 API 选择 解码耗时取决于数据量大小 对于小型数据完全可以 在主线程安全处理 对于库 最好提供非隔离的 API 让客户端自行决定是否卸载任务

    非隔离代码非常灵活 因为它可以从任何上下文中调用: 如果你在主 Actor 上调用它 它会保持在主 Actor 上执行 如果你从后台线程调用它 它就保留在后台线程上 这种特性使它成为 通用库的理想默认选择 当工作被调度至后台时 系统会自动分配 后台线程执行任务 并发线程池包含系统所有后台线程 数量可动态调整 在 Watch 等小型设备上 并发线程池可能只有 1-2 个线程 而多核系统则拥有更多后台线程 任务运行在哪个后台线程并不重要 系统会自动优化资源分配 例如 当任务挂起时 原线程会立即执行其他就绪任务 当任务恢复时 则可能被调度到 线程池中的任意可用线程 这些线程可能与初始线程不同

    引入并发机制后 我们需要 在不同线程间共享数据 但众所周知 在并发代码中 共享可变状态极易引发 难以调试的运行时错误 Swift 可以帮助你 在编译时发现这些错误 让你安全地编写并发代码 每次在主 Actor 与并发线程池间切换时 都会涉及跨线程数据共享 当从 UI 获取 URL 时 它会从主 Actor 传递到 后台线程以获取图像 获取的图像数据 随后被传递给解码环节 解码完成后 再将图像与 self 一起传回主 Actor Swift 会确保所有这些值 在并发代码中的访问安全性 假设 UI 更新触发了 涉及这个 URL 的额外任务 会发生什么情况 幸运的是 URL 作为值类型 当被拷贝到后台线程时 后台线程获得的是 独立于主线程的副本 如果用户通过 UI 输入了新 URL 主线程代码可以自由使用或修改副本 并且这些更改不会影响 后台线程使用的值 这意味着共享 URL 等 值类型是安全的 因为实际上并不存在真正的共享: 每个副本都完全独立

    值类型从一开始就是 Swift 的重要特性 所有基础类型 如字符串、 整数、日期 都是值类型

    包含值类型的集合 如字典、数组同样如此 存储值类型的结构体和枚举 如这个 Post 结构体 也不例外 我们将可安全并发共享的类型 称为 Sendable 类型 Sendable 是一个协议 任何符合 这个协议的类型都能安全共享 像 Array 这样的集合类型 对 Sendable 有条件遵循 只有当它们的元素为 Sendable 时 集合本身才是 Sendable 当结构体和枚举的所有 实例数据都符合 Sendable 时 它们也可以被标记为 Sendable 而主 Actor 类型隐式符合 Sendable 无需显式声明 Actor 如主 Actor 通过确保 每次只能被一个任务访问 来保护非 Sendable 状态的安全 Actor 可能存储传入方法的数值 也可能从它的方法返回 受保护状态的引用 当值类型传入或传出 Actor 时 Swift 编译器会检查这个值 是否可安全用于并发代码 让我们重点了解一下 decodeImage 的异步调用

    decodeImage 作为实例方法 它会隐式传递 self 参数

    这里涉及两个值被传出主 Actor 以及一个结果值被传回 主 Actor 的情况 “self”作为主 Actor 隔离的 图像模型类实例 主 Actor 会保护可变状态 使得向后台线程 传递类引用是安全的 而 Data 作为值类型 天然符合 Sendable

    关键在于图像类型 它可能是类似 Data 的值类型 自然符合 Sendable 但我们现在要讨论的是 像类这样不符合 Sendable 的类型 类属于引用类型 当将一个变量赋值给另一个变量时 它们指向内存中的同一对象 当通过某个变量修改对象时 例如缩放图像 所有指向同一对象的变量 都会立即看到这些变化 虽然 fetchAndDisplayImage 并未并发使用图像值 但 decodeImage 在后台运行时 无法访问任何 受 Actor 保护的状态 它仅从给定数据创建新的图像实例 由于这个图像不会 被任何并发代码引用 因此可安全传回 主 Actor 并显示在 UI 中 让我们看看引入并发后的情况 首先 scaleAndDisplay 方法 在主线程加载新图像 Image 变量指向包含猫图片的对象 接着函数在并发线程池创建任务 这个任务获取图像的副本 最后主线程继续执行图像显示 此时问题显现 后台线程正在修改图像: 调整宽高比 并将像素替换为缩放后的版本 而主线程却仍 基于旧尺寸遍历像素 这就形成了数据争用 这可能导致 UI 显示异常 更常见的是当程序尝试访问 像素数组边界外的内容时引发崩溃 当代码尝试共享非 Sendable 类型时 Swift 并发机制会通过编译器错误 来预防数据争用 本例中 编译器正提示 并发任务在捕获 image 对象 而这个对象同时被 主 Actor 用于图像显示 要修正这个问题 我们必须确保避免 并发共享同一对象 若要在 UI 中呈现图像特效 正确的解决方案是等待缩放 完成后再显示图像 我们可以将所有三个操作 移至同一任务中 以确保顺序执行 由于 displayImage 必须 在主 Actor 上运行 我们需要通过 await 从并发任务中调用它 如果直接将 scaleAndDisplay 改为异步方法 还能进一步简化代码 无需新建任务 直接在调用 scaleAndDisplay 的 任务中按顺序执行这三个操作 当我们将图像传至 主 Actor 进行 UI 展示后 主 Actor 可以自由存储 对图像的引用 例如缓存图像对象 若试图在图像 显示在 UI 后再次修改 编译器会抛出非安全并发访问错误 解决方案是 在图像传送至 主 Actor 前完成所有修改 若使用类作为数据模型 这些模型类通常 初始运行于主 Actor 以便部分数据可展示在 UI 中 当最终决定需要在后台线程操作时 可将它标记为 nonisolated 但通常不应使它们符合 Sendable 因为你不会希望模型的部分数据 在主线程更新 而另一部分在后台线程更新 保持模型类的非 Sendable 特性 可预防这类并发修改 同时也更简便 因为使类符合 Sendable 通常需要采用锁等底层同步机制 与类一样 闭包也可能创建共享状态 这里有一个类似之前 缩放显示图像功能的函数 它先创建图像对象 然后调用 perform(afterDelay:) 并传入一个用于缩放图像的闭包 这个闭包持有 对同一图像的另一个引用 我们称之为对图像变量的捕获 与非 Sendable 类相同 只要不被并发调用 具有共享状态的闭包仍是安全的 仅当需要并发共享时 才应将函数类型标记为 Sendable

    每当数据在 Actor 与任务间 传递时 都会触发 Sendable 检查 这是为了防止数据争用 导致 App 出现错误 许多常见类型都符合 Sendable 可安全用于并发任务间共享 而类和闭包可能包含 非安全共享的可变状态 因此应确保每次仅被单一任务使用

    你仍可将对象在任务间传递 但务必在传递前完成所有修改操作 将异步任务移至后台线程 能释放主线程 确保 App 响应流畅 若发现主 Actor 上存储过多数据 导致异步任务需要频繁 “上报”主线程 此时可以引入更多 Actor

    随着 App 演进 主 Actor 承载的状态可能不断增长 例如新增网络访问管理等子系统后 网络管理器维护的开放连接集合 等状态都会驻留主 Actor 每次网络数据获取 都需要访问这些状态 当我们开始使用这些额外的子系统时 先前的“获取-显示图像”任务 变得复杂: 虽然尝试在后台线程运行 却不得不跳转到主线程 因为于网络管理器的数据 必须通过主线程访问 最终可能导致 多个任务同时争抢 主 Actor 执行权限 虽然单个操作可能很快 但当大量任务频繁跳转时 仍会导致 UI 卡顿 之前我们通过将代码封装到 @concurrent 函数中 成功将它移出主线程 但当前瓶颈在于访问网络管理器数据 为此 可以引入 专属的网络管理器 Actor 和主 Actor 一样 Actor 也会隔离自己的数据 因此 只有在这个 Actor 上运行时 才能访问这些数据 除了主 Actor 你还可以自定义 Actor 类型 这类类型类似于主 Actor 类 它们隔离数据 确保同一时间只有一个线程能访问 同时作为 Sendable 类型 Actor 对象可安全共享 但与主 Actor 不同 程序中可存在 多个独立 Actor 实例 且它们不像主 Actor 那样 绑定到单个线程 因此将部分状态从 主 Actor 迁移至 Actor 对象 可以让更多代码在后台线程执行 从而保持主线程畅通 确保 UI 响应流畅

    当主 Actor 存储的数据导致 过多代码运行在主线程时 就应使用 Actor 这时 你可以将非 UI 部分代码 如网络管理模块的数据 分离到新 Actor 中

    但需注意 App 中大多数类 并不适合作为 Actor 面向 UI 的类应保持在主 Actor 以便直接与 UI 状态交互 模型类通常也应驻留 主 Actor 与 UI 交互 或保持非 Sendable 以避免引发对模型的大量并发访问 本次分享从单线程代码开始 随着需求增长 我们引入了 异步任务来隐藏延迟 采用并发代码在后台线程运行 并通过 Actor 将数据访问 移出主线程 事实上 多数 App 都会 遵循类似的演进路径

    使用性能分析工具来确定 何时以及将哪些代码移出主线程 Swift 并发机制将帮助你 正确地将这些代码与主线程分离 从而提高 App 的性能和响应速度

    我们为你的 App 提供了 一些推荐的构建设置 来协助引入并发 “渐进式并发”设置启用了 一系列即将推出的功能 使并发编程更加容易 建议所有项目都采用这个设置 对于主要与 UI 交互的 Swift 模块 比如你的主 App 模块 我们还建议将默认 Actor 隔离 设置为“主 Actor” 这样 除非你另有说明 否则代码将在主 Actor 上运行 这些设置共同作用 使单线程 App 更易于编写 并在需要时为引入并发 提供更便捷的路径 Swift 并发是一种 旨在帮助改进 App 的工具 当你发现 App 存在性能问题时 可以用它来引入异步或并发代码 Swift 6 迁移指南可以帮助解答 更多关于并发和 数据争用安全实现路径的疑问 要了解如何将本次讲座中的概念 应用于示例 App 请观看我们“跟着视频学编程”的 配套讲座 谢谢

    • 3:20 - Single-threaded program

      var greeting = "Hello, World!"
      
      func readArguments() { }
      
      func greet() {
        print(greeting)
      }
      
      readArguments()
      greet()
    • 4:13 - Data types in a the app

      struct Image {
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 4:57 - Load and display a local image

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) throws {
          let data = try Data(contentsOf: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 5:36 - Fetch and display an image over the network

      import Foundation
      
      struct Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) throws {
          let (data, _) = try URLSession.shared.data(from: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 6:10 - Fetch and display image over the network asynchronously

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 7:31 - Creating a task to perform asynchronous work

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
        var url: URL = URL("https://swift.org")!
      
        func onTapEvent() {
          Task {
            do {
      	try await fetchAndDisplayImage(url: url)
            } catch let error {
              displayError(error)
            }
          }
        }
      
        func displayError(_ error: any Error) {
        }
      
        func fetchAndDisplayImage(url: URL) async throws {
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 9:15 - Ordered operations in a task

      import Foundation
      
      class Image {
        func applyImageEffect() async { }
      }
      
      final class ImageModel {
        func displayImage(_ image: Image) {
        }
      
        func loadImage() async -> Image {
          Image()
        }
        
        func onButtonTap() {
          Task {
            let image = await loadImage()
            await image.applyImageEffect()
            displayImage(image)
          }
        }
      }
    • 9:38 - Fetch and display image over the network asynchronously

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
    • 10:40 - Fetch and display image over the network asynchronously

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data, at url: URL) -> Image {
          Image()
        }
      }
    • 11:11 - Fetch over network asynchronously and decode concurrently

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          Image()
        }
      }
    • 11:30 - Implementation of decodeImage

      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          if let image = cachedImage[url] {
            return image
          }
      
          // decode image
          let image = Image()
          cachedImage[url] = image
          return image
        }
      }
    • 12:37 - Correct implementation of fetchAndDisplayImage with caching and concurrency

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          if let image = cachedImage[url] {
            view.displayImage(image)
            return
          }
      
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data) async -> Image {
          // decode image
          Image()
        }
      }
    • 13:30 - JSONDecoder API should be non isolated

      // Foundation
      import Foundation
      
      nonisolated
      public class JSONDecoder {
        public func decode<T: Decodable>(_ type: T.Type, from data: Data) -> T {
          fatalError("not implemented")
        }
      }
    • 15:18 - Fetch over network asynchronously and decode concurrently

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          Image()
        }
      }
    • 16:30 - Example of value types

      // Value types are common in Swift
      import Foundation
      
      struct Post {
        var author: String
        var title: String
        var date: Date
        var categories: [String]
      }
    • 16:56 - Sendable value types

      import Foundation
      
      // Value types are Sendable
      extension URL: Sendable {}
      
      // Collections of Sendable elements
      extension Array: Sendable where Element: Sendable {}
      
      // Structs and enums with Sendable storage
      struct ImageRequest: Sendable {
        var url: URL
      }
      
      // Main-actor types are implicitly Sendable
      @MainActor class ImageModel {}
    • 17:25 - Fetch over network asynchronously and decode concurrently

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await self.decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          Image()
        }
      }
    • 18:34 - MyImage class with reference semantics

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scale(by factor: Double) {
        }
      }
      
      let image = MyImage()
      let otherImage = image // refers to the same object as 'image'
      image.scale(by: 0.5)   // also changes otherImage!
    • 19:19 - Concurrently scaling while displaying an image is a data race

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        func scaleAndDisplay(imageName: String) {
          let image = loadImage(imageName)
          Task { @concurrent in
            image.scaleImage(by: 0.5)
          }
      
          view.displayImage(image)
        }
        // Slide content end
      
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 20:38 - Scaling and then displaying an image eliminates the data race

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        func scaleAndDisplay(imageName: String) {
          Task { @concurrent in
            let image = loadImage(imageName)
            image.scaleImage(by: 0.5)
            await view.displayImage(image)
          }
        }
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 20:54 - Scaling and then displaying an image within a concurrent asynchronous function

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        @concurrent
        func scaleAndDisplay(imageName: String) async {
          let image = loadImage(imageName)
          image.scaleImage(by: 0.5)
          await view.displayImage(image)
        }
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 21:11 - Scaling, then displaying and concurrently modifying an image is a data race

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      
        func applyAnotherEffect() {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        @concurrent
        func scaleAndDisplay(imageName: String) async {
          let image = loadImage(imageName)
          image.scaleImage(by: 0.5)
          await view.displayImage(image)
          image.applyAnotherEffect()
        }
        // Slide content end
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 21:20 - Applying image transforms before sending to the main actor

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      
        func applyAnotherEffect() {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        @concurrent
        func scaleAndDisplay(imageName: String) async {
          let image = loadImage(imageName)
          image.scaleImage(by: 0.5)
          image.applyAnotherEffect()
          await view.displayImage(image)
        }
        // Slide content end
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 22:06 - Closures create shared state

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scale(by factor: Double) {
        }
      
        func applyAnotherEffect() {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        @concurrent
        func scaleAndDisplay(imageName: String) async throws {
          let image = loadImage(imageName)
          try await perform(afterDelay: 0.1) {
            image.scale(by: 0.5)
          }
          await view.displayImage(image)
        }
      
        nonisolated
        func perform(afterDelay delay: Double, body: () -> Void) async throws {
          try await Task.sleep(for: .seconds(delay))
          body()
        }
        // Slide content end
        
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }pet.
    • 23:47 - Network manager class

      import Foundation
      
      nonisolated class MyImage { }
      
      struct Connection {
        func data(from url: URL) async throws -> Data { Data() }
      }
      
      final class NetworkManager {
        var openConnections: [URL: Connection] = [:]
      
        func openConnection(for url: URL) async -> Connection {
          if let connection = openConnections[url] {
            return connection
          }
      
          let connection = Connection()
          openConnections[url] = connection
          return connection
        }
      
        func closeConnection(_ connection: Connection, for url: URL) async {
          openConnections.removeValue(forKey: url)
        }
      
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
        let networkManager: NetworkManager = NetworkManager()
      
        func fetchAndDisplayImage(url: URL) async throws {
          if let image = cachedImage[url] {
            view.displayImage(image)
            return
          }
      
          let connection = await networkManager.openConnection(for: url)
          let data = try await connection.data(from: url)
          await networkManager.closeConnection(connection, for: url)
      
          let image = await decodeImage(data)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data) async -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 25:10 - Network manager as an actor

      import Foundation
      
      nonisolated class MyImage { }
      
      struct Connection {
        func data(from url: URL) async throws -> Data { Data() }
      }
      
      actor NetworkManager {
        var openConnections: [URL: Connection] = [:]
      
        func openConnection(for url: URL) async -> Connection {
          if let connection = openConnections[url] {
            return connection
          }
      
          let connection = Connection()
          openConnections[url] = connection
          return connection
        }
      
        func closeConnection(_ connection: Connection, for url: URL) async {
          openConnections.removeValue(forKey: url)
        }
      
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
        let networkManager: NetworkManager = NetworkManager()
      
        func fetchAndDisplayImage(url: URL) async throws {
          if let image = cachedImage[url] {
            view.displayImage(image)
            return
          }
      
          let connection = await networkManager.openConnection(for: url)
          let data = try await connection.data(from: url)
          await networkManager.closeConnection(connection, for: url)
      
          let image = await decodeImage(data)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data) async -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 0:00 - 简介
    • Swift 并发使 App 能够同时执行多个任务,从而提升响应速度并将计算任务转移到后台。Swift 的并发模型通过显式引入并发、识别并发任务之间共享的数据以及在编译时识别潜在的数据争用,使正确编写并发代码变得更容易。 App 会首先在主线程上运行所有代码。随着复杂性增加,可以引入异步任务来执行网络访问等高延迟操作。后台线程可用于执行计算密集型任务。Swift 提供了 Actor 和 Task 等工具来表达这些并发操作。

    • 3:17 - 单线程代码
    • 在 Swift 中,单线程代码在主线程上运行,主线程与主 Actor 分离。主 Actor 上没有并发,因为只有一个主线程可以运行它。你可以使用 @MainActor 注释来指定主 Actor 上的数据或代码。 Swift 将确保主 Actor 代码只在主线程上运行,并且只能从主线程访问主 Actor 数据。我们称此类代码与主 Actor 隔离。默认情况下,Swift 使用主 Actor 保护主线程代码,从而确保可以自由访问共享状态。 默认情况下,使用主 Actor 保护代码的功能由构建设置驱动。这主要用于你的 App 主模块以及任何专注于 UI 交互的模块。

    • 6:00 - 异步任务
    • Swift 并发使用 async 和 await 将函数设为非阻塞函数,从而允许在等待数据 (如网络请求) 的同时运行其他工作。这可以通过将函数分解为在等待事件之前和之后运行的部分,来防止挂起并提高 UI 响应速度。

    • 7:24 - 交错执行
    • 异步函数在一个任务中运行,将独立于其他任务执行。单线程可以在独立任务就绪后将它们交替运行,也就是使用“交错”机制。这样可以通过避免空闲时间来提高性能,并高效利用系统资源。 当同时执行多个独立操作时,多个异步任务处于有效状态。当按特定顺序执行工作时,请使用单个任务。 通常在单线程中使用异步任务就足够了。如果主线程负担过重,Instruments 等性能分析工具可以帮助在引入并发之前识别出需要优化的瓶颈。

    • 10:22 - 并发简介
    • 并发允许部分代码在后台线程上与主线程并行运行,通过使用系统中的更多 CPU 内核来更快地完成工作。为了提高性能,这里的示例引入了并发以在后台线程上运行代码,从而释放主线程。

    • 11:07 - 并发函数
    • 应用 @concurrent 属性,就可以指示 Swift 在后台运行一个函数。Swift 编译器会突出显示主 Actor 上的数据访问,以安全地引入并发。 为了确保工作同步进行,一种最佳实践是将主 Actor 代码移至始终在主线程上运行的调用程序中。

    • 13:10 - 非限定代码
    • @concurrent 函数总是会脱离 Actor 运行。“nonisolated”关键字允许客户端选择运行代码的位置 (main 或 background)。对于通用资源库,建议提供非隔离的 API,并让客户端自行决定是否转移工作。 有关更多并发选项,请参阅 WWDC23 中的“超越结构化并发的基础”。

    • 14:13 - 并发线程池
    • 将工作转移到后台时,系统会管理在并发线程池中的线程上的工作调度。较小的设备可能在池中拥有较少的线程,而具有较多内核的较大系统则将具有较多线程。任务会分配给池中的可用线程,这些线程可能会随着任务的暂停和恢复而变化,从而优化资源利用率。

    • 14:58 - 共享数据
    • 在使用并发并在不同线程之间共享数据时,访问共享的可变状态可能会引入运行时错误。Swift 的设计将通过提供编译时检查来帮助缓解这个问题,使开发者能够更自信地编写并发代码。

    • 15:49 - 值类型
    • 在处理并发任务时,使用值类型具有显著优势。当将值类型拷贝到后台线程时,会创建一个独立的副本,从而确保在主线程上所做的任何更改都不会影响后台线程的值。这种独立性使得值类型可以安全地在线程之间共享。 符合“Sendable”协议的值类型始终可以安全地并发共享。主 Actor 类型隐式符合 Sendable。

    • 17:16 - Actor 限定类型
    • Swift Actor 通过确保单任务访问来保护非 Sendable 状态。当与 Actor 之间互相发送值时,Swift 编译器会验证安全性。

    • 18:30 - 类
    • 在 Swift 中,类是引用类型,这意味着通过一个变量对对象进行的更改将对所有指向这个对象的变量可见。 当多个线程同时访问和修改同一个非 Sendable 对象时,可能会导致数据争用、崩溃或故障。Swift 的并发系统会强制仅在 Actor 和 Task 之间共享 Sendable 类型,从而防止在编译时发生这种情况。 为了避免数据争用,确保可变对象不被并发共享至关重要。在将对象发送到另一个 Task 或 Actor 进行显示或处理之前,请先完成对对象的修改。如果需要在后台线程上修改对象,请将它设置为“nonisolated”状态,但不要设置为 Sendable。 具有共享状态的闭包只要不被并发调用,也可以是安全的。

    • 23:18 - Actor
    • 随着 App 的发展,主 Actor 可能会管理大量状态,从而导致频繁的上下文切换。引入 Actor 可以缓解这种情况。Actor 会隔离这些数据,一次只允许一个线程进行访问,从而避免争用。 将状态从主 Actor 转移到专用 Actor,就可以在后台线程上并发执行更多代码。这样可以释放主线程,以确保响应能力。面向 UI 的类和模型类通常需要保留在主 Actor 上,或者保持非 Sendable 状态不变。

    • 26:12 - 总结
    • App 通常最初是单线程的,然后逐渐演变为使用异步任务、并发代码和 Actor 以提升性能。Swift 并发有助于实现这一转变,使代码更容易从主线程移出并提升响应速度。Instruments 等性能分析工具有助于识别何时以及哪些代码需要从主线程移出。请使用推荐的构建设置来帮助简化并发的引入,并使用“渐进式并发”设置实现一系列即将推出的功能,从而更轻松地使用并发。

Developer Footer

  • 视频
  • WWDC25
  • 采用 Swift 并发
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载 (英文)
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    获取 Apple Developer App。
    版权所有 © 2025 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则