大多数浏览器和
Developer App 均支持流媒体播放。
-
利用 Swift Actor 保护可变状态
当两个单独线程同时访问同一可变状态时,会发生数据竞争。它们构建起来很简单,但出了名的难以调试。了解如何才能停止与 Swift Actor 的这些数据竞争,从而帮助同步您的代码中的数据访问。探索 Actor 如何工作以及如何相互共享值。了解 Actor 隔离如何影响协议一致性。最后,一起来探索主要 Actor,这是一种确保您的代码始终根据需要在主要线程上运行的新方式。为了充分了解本节内容,我们建议先观看“认识 Swift 中的 async/await”。
资源
- SE-0302: Sendable and @Sendable closures
- SE-0306: Actors
- SE-0313: Improved control over actor isolation
- SE-0316: Global actors
- The Swift Programming Language: Concurrency
相关视频
WWDC22
WWDC21
-
下载
♪低音音乐播放♪ ♪ 达瑞欧瑞辛:大家好 我叫达瑞欧瑞辛 我是Apple的Swift团队的工程师 今天 我和同事道格 将讨论Swift中的Actor 以及在并行 Swift App 中 如何利用它们来保护可变状态 编写并行程序的 根本难题之一 是避免数据竞争 当两个单独的线程同时访问 相同的数据时 会发生数据竞争 并且这之中至少一个访问是写入 数据竞争构建起来很简单 但出了名的难以除错 这是一个带有一个操作的 简单计数器类 会增加计数器并返回其新值 假设我们继续并尝试 从两个并行任务增加 这是一个坏主意 根据执行的时间 我们可能会得到1然后2 或者2然后是1 这是意料之中的 在这两种情况下 计数器将保持一致状态 但是因为我们引入了数据竞赛 我们也可以得到1和1 如果两个任务都读取0 并写入一个1 或甚至是2和2 如果返回语句发生在 两个增量操作之后 数据竞争出了名地难以避免和除错 他们需要非本地推理 因为数据访问 导致竞争可能在程序的不同部分 它们是不确定的 因为操作系统的 调度程序在每次运行程序时 可能会以不同的方式交错并行任务 数据竞争是由共享的可变状态引起的 如果你的数据没有更改 或未在多个并行任务之间共享 你不能在上面进行数据竞赛 避免数据竞争的一种方法是 通过使用值语义 消除共享的可变状态 对于值类型的变量 所有变异都是局部的 此外 值语义类型的“let”属性 是真正不可变的 因此从不同的并行任务 访问它们是安全的 Swift自成立以来一直在推广值语义 因为它们使我们的程序更容易推理 这些相同的东西也使它们 可以安全地用于并行程序 在这个例子中 我们创建了一个 包含一些值的数组 接下来 我们将该数组分配 给第二个变量 现在我们为数组的每个副本 附加一个不同的值 当我们在最后打印两个数组时 我们看到两个副本都包含值 数组被初始化 但每个附加值只存在于 相应的、我们将它们 附加进去的副本中 Swift标准库中的大多数类型 都具有值语义 包括集合类型 例如字典 或在这个例子中是数组 现在我们已经确定值语义 解决了我们所有的数据竞争 让我们继续 通过将其转换为结构体 来使我们的计数器成为值类型 我们还必须将增量函数标记为变异 所以它可以修改值属性 当我们现在尝试修改计数器时 我们会得到一个编译器错误 因为计数器是一个let 这可以防止我们对其进行变异 现在 似乎很诱人的是 改变计数器变量 到var以使其可变 但这会让我们再次面临竞争条件 因为计数器将再将通过 两个并行任务被引用 幸运的是 我们的编译器很靠谱 并且不允许我们编译 这个不安全的代码 我们可以改为分配计数器 到每个并行任务中的局部可变变量 当我们现在执行我们的例子时 它将始终 为两个并行任务打印1 但即使我们的代码现在是无竞争的 这种行为不再是我们想要的 这说明了在需要共享 可变状态的地方 还是有案例的 当我们在并行程序中共享可变状态时 我们需要某种形式的同步 确保并行使用我们共享的可变状态 不会导致数据竞争 有许多用于同步的原语 从像原子和锁这样的低级工具 到更高级别的构造 如串行调度队列 这些原语中的每一个 都有其不同的优势 但它们都具有相同的严重弱点 他们需要仔细的纪律才能准确地使用 而且是每一次 否则我们会以数据竞争告终 这时候就轮到Actor出场了 Actor是一种共享可变状态的 同步机制 Actor有自己的状态 并且该状态与程序的其余部分隔离 访问该状态的唯一方法 是通过Actor 并且每当你通过Actor时 Actor的同步机制确保 没有其他代码同时 访问Actor的状态 这为我们提供了相同的 与我们从手动使用锁 或者串行调度队列 得到的互斥属性 至于Actor 这是Swift提供的基本保证 你不能忘记执行同步 因为如果你这么做 Swift会产生一个编译器错误 Actor是Swift中的一种新类型 它们提供与Swift中 所有命名类型相同的功能 它们可以有属性、方法 初始值设定项 下标 诸如此类的 它们可以符合协议 并通过扩展进行扩充 和类一样 它们是引用类型 因为Actor的目的是 表达共享的可变状态 实际上 Actor类型的主要区别特征 是它们从程序的其余部分 隔离了它们的实例数据 并确保对这些数据的同步访问 他们所有的特殊行为 都遵循这些核心思想 在这里 我们将我们的计数器 定义为Actor类型 我们仍然有计数器的 实例属性值 以及增加该值并返回新值的 增量方法 不同之处在于 Actor将确保 不会同时访问该值 在这种情况下 这意味着增量方法 在调用时将运行到完成 而没有在Actor上执行任何其他代码 该保证消除了Actor状态 数据竞争的可能性 让我们回到我们的数据竞争示例 我们再次有两个并行任务 试图要增加相同的计数器 Actor的内部同步机制 会在增量调用可以开始之前 确保另一个的执行完成 所以我们可以得到1和2或2和1 因为两者都是有效的并行执行 但我们不能两次得到相同的计数 或跳过任何值 因为Actor的内部同步 已经消除了Actor状态的 数据竞争的可能性 让我们考虑一下 当两个并行任务都尝试 同时增加计数器时会怎么样 其中一个会第一个到达那里 另一个必须等待轮到它 但是我们如何确保第二个任务 可以耐心地等待Actor的机会呢? Swift针对这有一个机制 每当你从外部与Actor互动时 都是异步进行的 如果Actor很忙 那么你的代码将卡顿 所以你运行的CPU 可以做其他有用的工作 当Actor再次获得自由时 它会唤醒你的代码“恢复执行” 所以调用可以在Actor上运行 本例子中的await关键词表示着 对Actor的异步调用 可能会涉及这样的挂起 通过添加不必要的缓慢复位操作 我们来进一步扩展我们的反例 此操作将值设置回0 然后用适当的次数调用增量 使计数器达到新值 这个resetSlowly方法 会在反Actor类型中的扩展 被定义 所以它在Actor内部 这意味着它可以 直接访问Actor的状态 它将计数器值重置为0 它还可以同步调用Actor的其他方法 例如在调用增量时 不需要await 因为我们已经知道 我们正在运行Actor 这是Actor的重要属性 Actor上的同步代码从运行到完成 总是不被打扰 所以我们可以按顺序推理同步代码 无需考虑并行性对我们的 Actor状态的影响 我们已经强调过我们的同步代码 是不间断运行的 但是Actor经常会相互 或与系统中的其他异步代码交互 让我们花几分钟时间谈谈异步代码 和Actor 但首先 我们需要一个更好的例子 在这里 我们正在构建一个 图像下载器Actor 它负责从另一个服务中 下载图像 它还将下载的图像存储在缓存中 以避免多次下载相同的图像 逻辑流程很简单 检查缓存 下载图像 然后在返回之前将图像记录在缓存中 因为我们是Actor 此代码不受低级数据竞争的影响 可以同时下载任意数量的图像 Actor的同步机制保证 只有一个任务可以执行代码 一次访问缓存实例属性 因此无法破坏缓存 也就是说 这里的await关键词 传达了非常重要的信息 每当发生await时 就意味着该函数可以在此时暂停 它放弃自己的CPU 以便程序中的其他代码可以执行 这会影响整个程序状态 在你的功能恢复时 整个程序状态将发生变化 重要的是确保你 没有对await之前的状态 做出可能在await之后 不成立的假设 想象一下 我们有 两个不同的并行任务 试图同时获取相同的图像 第一个看到没有缓存条目 继续从服务器开始下载图像 然后被挂起 因为下载需要一段时间 当第一个任务是下载图像时 可能会在相同URL下 将新图像部署到服务器 现在 第二个并行任务尝试获取 该URL下的图像 它也看不到缓存条目 因为第一次下载尚未完成 然后开始第二次下载图像 它也会在下载完成时暂停 过了一会儿 其中一个下载… 让我们假设它是第一个 将会完成 并且它的任务将在Actor上恢复执行 它填充缓存 并返回猫的结果图像 现在第二个任务已经完成下载 所以它被唤醒 它用它得到的悲伤猫的图像 覆盖缓存中的相同条目 因此 即使缓存中已经填充了图像 我们现在也会为相同的URL 获得不同的图像 这有点意外 我们期望一旦缓存了一个图像 我们总会为相同的URL 获取相同的图像 所以我们的用户界面保持一致 至少在我们手动清除缓存之前是这样 但是在这里 缓存的图像 会意外地变动 我们没有任何低级别的数据竞争 但是因为我们在await中 对状态进行了假设 我们最终发现了一个潜在的错误 这里的解决方法是 在await之后检查我们的假设 如果我们恢复时 缓存中已经有一个条目 我们保留原始版本并丢弃新版本 更好的解决方案是 完全避免冗余下载 我们已将该解决方案 放入与此视频相关的代码中 Actor可重入可防止死锁 并保证前进 但它要求你在每个await中 检查你的假设 为了更好地设计可重入性 请在同步代码中 执行Actor状态的突变 理想情况下 请在同步函数中进行 所以所有的状态变化 都被很好地封装了 状态更改可能涉及 暂时将我们的Actor 置于不一致的状态 确保在await之前恢复一致性 请记住 await是一个潜在的暂停点 如果你的代码被挂起 在你的代码恢复之前 程序和世界将继续前进 你对全局状态、时钟、计时器 或你的Actor所做的 任何假设 在await之后 都需要检查 现在我的同事道格会告诉你 更多关于Actor隔离的信息 道格? 道格格雷戈尔:谢谢 达里奥 Actor隔离是Actor类型 行为的基础 达瑞欧讨论了Swift语言模型 通过来自Actor外部的 异步交互 如何保证Actor隔离 在本节中 我们将讨论 Actor隔离如何 与其他语言功能交互 包括协议一致性、闭包和类 和其他类型一样 Actor可以遵守协议 只要他们能满足协议的要求 例如 让我们来把这个 LibraryAccount的Actor 符合Equatable协议 静态相等方法会根据ID号码 比较两个图书馆账户 因为该方法是静态的 所以没有自身的实例 所以它不与Actor隔离 相反 我们有两个Actor类型的参数 而这个静态方法是在这两个参数之外 这是可以的 因为该实现仅会访问 Actor上的不可变状态 让我们进一步扩展我们的示例 使我们的图书馆账户遵照 可散列的协议 这样做需要实现hash(into)操作 而我们可以这样做 然而 Swift编译器会抱怨 这种一致性是不被允许的 怎么一回事? 好吧 以这种方式遵照可散列意味着 可以从Actor外部调用此函数 但是hash(into)不是异步的 所以没有办法保持Actor隔离 为了解决这个问题 我们可以使这个方法非隔离 非隔离意味着此方法 被视为在Actor之外 即使它在语法上 是在Actor身上描述的 这意味着它可以满足 可散列协议的同步要求 因为非隔离方法 被视为在Actor之外 他们不能在Actor上引用可变状态 这种方法很好 因为它指的是不可变的ID号码 如果我们尝试根据其他东西 例如借出书籍的数组 进行散列 我们会得到一个错误 因为从外部访问可变状态 将允许数据竞争 协议一致性就足够了 让我们来谈谈闭包 闭包是在一个函数中 定义的小函数 然后可以将其传递给另一个函数 以便稍后调用 像函数一样 闭包可能是 Actor隔离的 或者它可能是非隔离的 在这个例子中 我们将从我们 借出的每本书中阅读一些 并返回我们阅读的总页数 对reduce的调用 涉及执行读取的闭包 请注意 在此对readSome的调用中 没有await 那是因为这个闭包 它是在独立于Actor的函数 “read”中形成的 本身是Actor隔离的 我们知道这是安全的 因为reduce操作 将同步执行 并且无法将闭包转至 其他可能导致并行访问的线程 现在 我们来做一些不同的事情 我现在没有时间阅读 所以让我们稍后阅读 在这里 我们创建了一个分离的任务 一个分离的任务与Actor正在做的 其他工作同时执行闭包 因此 闭包不能在Actor上 否则我们会引入数据竞争 所以这个闭包不是孤立于Actor的 当它想要调用read方法时 它必须异步执行 如await所示 我们已经谈到了代码的Actor隔离 即代码是在Actor内部运行 还是在Actor外部运行 现在 让我们谈谈Actor隔离和数据 在我们的图书馆账户示例中 我们刻意避免说明 图书类型实际上是什么 我一直假设它是一个值类型 就像一个结构体 这是一个不错的选择 因为这意味着 图书馆账户Actor实例的所有状态 是自给自足的 如果我们继续调用这个方法 来随机选择一本书阅读 我们会得到一本我们可以阅读的书 我们对这本书所做的更改 不会影响Actor 反之亦然 然而 如果把这本书变成一个类 事情就有点不同了 我们的图书馆账户Actor现在引用 book类的实例 这本身不是问题 但是 当我们调用方法 随机选择书时会发生什么? 现在我们有了对Actor 可变状态的引用 而这已在Actor之外共享 我们创造了数据竞争的潜力 现在 如果我们去更新书名 修改会在Actor内 发生在可访问的状态 因为访问方法不在Actor上 这种修改最终可能会导致数据竞争 值类型和Actor 都可以安全地同时使用 但是类仍然会带来问题 我们有一个可以安全 同时使用的类型的名称 “可发送的” 可发送的类型是一种其值 可以在不同Actor之间 共享的类型 如果将一个值从一个地方 复制到另一个地方 并且两个地方都可以 安全地修改自己的副本 该值互不干扰 该类型可以是可发送的 值类型是可发送的 因为每个副本都是独立的 正如达瑞欧之前所说 Actor类型是可发送的 因为它们会同步对其可变状态的访问 类也可以是可发送的 但前提是它们被仔细地实现 例如 如果一个类及其所有子类 只保存不可变数据 那么它可以称为可发送的 或者如果类在内部执行同步 例如使用锁 为了确保安全的并行访问 它可以是可发送的 但大多数的类都不是这些 也不能是可发送的 函数不一定是可发送的 所以对于可以安全地跨Actor 传递的功能 有一种新的函数类型 我们很快会再回来说这些 你的Actor… 事实上 你所有的并行代码 应该主要根据可发送的类型进行通信 可发送类型能保护代码免受数据竞争 这是Swift最终将开始 静态检查的属性 在那时候 跨越Actor的界限 传递 不可发送的类型 将成为错误 如何知道一种类型是可发送的? 好吧 “可发送的”是一个协议 你声明你的类型符合“可发送” 与使用其他协议的方式相同 然后Swift将会检查 以确保你的类型 作为可发送类型是合理的 如果Book结构的所有存储属性都是 可发送类型 则它可以是可发送的 假设Author实际上是一个类 这意味着…因此作者的数组… 为不可发送 Swift将产生一个编译器错误 指示Book为不能发送 对于泛型类型 它们是否可发送 取决于它们的泛型参数 我们可以在适当的时候 使用条件一致性来传播可发送 例如 仅当它的两个通用参数 都是可发送的 配对类型才会是可发送的 使用相同的方法得出结论 可发送类型的数组本身就是可发送的 我们鼓励你将可发送的一致性 引进其值可以安全地同时共享的类型 在你的Actor中使用这些类型吧 然后当Swift开始跨Actor 强制执行可发送 你的代码将准备就绪 函数本身可以是可发送的 意味着跨Actor传递函数值 是安全的 这对于闭包被限制能做什么 来帮助防止数据竞争尤其重要 例如 一个可发送的闭包 不能捕获一个可变的局部变量 因为这将允许 局部变量上的数据竞争 闭包捕获的任何内容 都需要是可发送的 来确保闭包不能被用来 把不可发送类型移动到 跨Actor的边界 最后 一个同步的可发送的闭包 是不能Actor隔离的 因为这将允许代码 从外部在Actor身上运行 本次座谈中 我们实际上一直依赖着 可发送闭包的想法 创建分离任务的操作采用了 可发送的函数 这里写成函数类型中的@Sendable 还记得座谈开始时的反例吗? 我们试图构建一个值类型计数器 然后 我们尝试同时 从两个不同的闭包中修改它 这将是可变局部变量的数据竞争 但是 因为分离任务的闭包 是可发送的 Swift会在这里产生错误 可发送函数类型被用于指示 可以发生并行执行的位置 从而防止数据竞争 这是我们之前看到的另一个例子 因为分离任务的闭包是可发送的 我们知道它不应该与Actor隔离 因此 与它的交互必须是异步的 可发送类型和闭包通过检查可变状态 未在Actor之间共享 来帮助维护Actor隔离 并且不能同时修改 我们主要讨论的是Actor类型 以及它们如何与协议、闭包 和可发送类型 还有一位Actor要讨论… 它很特殊 我们称之为主要Actor 在构建 App 时 你需要考虑到主线程 它是核心用户界面渲染发生的地方 也是用户事件交互 和被处理的地方 与UI共同进行的操作 通常需要从主线程执行 但是 你不想在主线程上 完成所有工作 如果你在主线程上 做了太多的工作 比如说 因为你有一些缓慢的输入/输出操作 或以服务器阻止交互 你的UI将会冻结 因此 当主线程与UI交互时 你需要小心地在主线程上进行工作 但在计算成本高 或等待时间长的操作上 请迅速脱离主线 所以 当我们可以的时候 我们会在主线程之外工作 然后每当你进行特定操作时 在你的代码中调用 必须在主线程上执行的 DispatchQueue.main.async 从机制的细节退一步回来看 这段代码的结构看起来有点眼熟 其实和主线程交互 很像与Actor互动 如果你知道你已经在主线程上运行 你可以安全地访问和更新你的UI状态 如果你不在主线程上运行 你需要与它异步交互 这正是Actor的工作方式 有一个特殊的Actor来描述主线程 我们称之为主要Actor 主要Actor是 代表主线程的Actor 它在两个重要方面与普通Actor不同 首先 主要Actor会通过主调度队列 执行其所有同步 这意味着从运行时的角度来看 主要Actor是可以 通过DispatchQueue.main互换的 第二 需要在主线程上的代码和数据 散落在各处 它在SwiftUI、AppKit、UIKit 和其他系统框架中 它分布在你自己的视图、视图控制器 以及数据模型中面向UI的部分 使用Swift并行性 你可以用 主要Actor特性标记声明 表示必须在 主要Actor上执行 我们已经完成了这里的检出操作 所以它总是在主要Actor身上运行 如果你从主要Actor之外调用它 你需要等待 以便执行调用 在主线程上异步执行 通过标记必须在主线程上运行的代码 作为主要Actor 也不需要再更多的猜测 关于何时使用DispatchQueue.main了 Swift会确保这段代码 总是在主线程上执行 类型也可以放在主要Actor身上 这使得他们的所有成员和子类 成为主要Actor 这对必须与UI交互的 代码库的部分来说很有用 大多数东西都需要在主线程上运行 个别方法可以通过 nonisolated关键词来退出 通过来自普通Actor的 你熟悉的相同规则 通过为面向UI的类型 使用主要Actor 和操作 并介绍自己的Actor 用于管理其他程序状态 你可以构建你的 App 以确保安全 以及正确的并行性使用方式 在本次座谈中 我们讨论了 Actor如何使用Actor隔离 保护其可变状态免受并行访问 并通过要求 从Actor外部进行异步访问 来将执行给序列化 使用Actor在Swift代码中 构建安全的 并行抽象吧 在实现你的Actor时 以及在任何异步代码中 请始终为可重入而设计 而在你代码中的await 意味着世界可以继续前进 并使你的假设无效 值类型和Actor会协同工作 来消除数据竞争 留意那些不处理 它们自己同步的类 以及其他重新引入 共享可变状态的、不可发送的类型 最后 在与UI交互的代码上 使用主要Actor以确保 必须在主线程上的代码 会总在主线程上运行 了解有关如何在你自己的应用中 使用Actor的更多信息 请查看我们的座谈 有关更新Swift并行的 App 并了解更多有关实施Swift的 并行模型的信息 包括Actor 请查看我们的《幕后花絮》座谈 Actor是Swift并行模型的核心部分 它们与async/await 和结构化并行一起工作 来让构建正确高效的 并行程序更为容易 等不及看到你们的成品了 ♪
-
-
0:42 - Data races make concurrency hard
class Counter { var value = 0 func increment() -> Int { value = value + 1 return value } } let counter = Counter() Task.detached { print(counter.increment()) // data race } Task.detached { print(counter.increment()) // data race }
-
2:20 - Value semantics help eliminate data races
var array1 = [1, 2] var array2 = array1 array1.append(3) array2.append(4) print(array1) // [1, 2, 3] print(array2) // [1, 2, 4]
-
2:59 - Sometimes shared mutable state is required
struct Counter { var value = 0 mutating func increment() -> Int { value = value + 1 return value } } let counter = Counter() Task.detached { var counter = counter print(counter.increment()) // always prints 1 } Task.detached { var counter = counter print(counter.increment()) // always prints 1 }
-
5:23 - Actor isolation prevents unsynchronized access
actor Counter { var value = 0 func increment() -> Int { value = value + 1 return value } } let counter = Counter() Task.detached { print(await counter.increment()) } Task.detached { print(await counter.increment()) }
-
7:51 - Synchronous interation within an actor
extension Counter { func resetSlowly(to newValue: Int) { value = 0 for _ in 0..<newValue { increment() } assert(value == newValue) } }
-
9:02 - Check your assumptions after an await: The sad cat
actor ImageDownloader { private var cache: [URL: Image] = [:] func image(from url: URL) async throws -> Image? { if let cached = cache[url] { return cached } let image = try await downloadImage(from: url) // Potential bug: `cache` may have changed. cache[url] = image return image } }
-
11:50 - Check your assumptions after an await: One solution
actor ImageDownloader { private var cache: [URL: Image] = [:] func image(from url: URL) async throws -> Image? { if let cached = cache[url] { return cached } let image = try await downloadImage(from: url) // Replace the image only if it is still missing from the cache. cache[url] = cache[url, default: image] return cache[url] } }
-
11:59 - Check your assumptions after an await: A better solution
actor ImageDownloader { private enum CacheEntry { case inProgress(Task<Image, Error>) case ready(Image) } private var cache: [URL: CacheEntry] = [:] func image(from url: URL) async throws -> Image? { if let cached = cache[url] { switch cached { case .ready(let image): return image case .inProgress(let task): return try await task.value } } let task = Task { try await downloadImage(from: url) } cache[url] = .inProgress(task) do { let image = try await task.value cache[url] = .ready(image) return image } catch { cache[url] = nil throw error } } }
-
13:30 - Protocol conformance: Static declarations are outside the actor
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] } extension LibraryAccount: Equatable { static func ==(lhs: LibraryAccount, rhs: LibraryAccount) -> Bool { lhs.idNumber == rhs.idNumber } }
-
14:15 - Protocol conformance: Non-isolated declarations are outside the actor
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] } extension LibraryAccount: Hashable { nonisolated func hash(into hasher: inout Hasher) { hasher.combine(idNumber) } }
-
15:32 - Closures can be isolated to the actor
extension LibraryAccount { func readSome(_ book: Book) -> Int { ... } func read() -> Int { booksOnLoan.reduce(0) { book in readSome(book) } } }
-
16:29 - Closures executed in a detached task are not isolated to the actor
extension LibraryAccount { func readSome(_ book: Book) -> Int { ... } func read() -> Int { ... } func readLater() { Task.detached { await read() } } }
-
17:15 - Passing data into and out of actors: structs
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] func selectRandomBook() -> Book? { ... } } struct Book { var title: String var authors: [Author] } func visit(_ account: LibraryAccount) async { guard var book = await account.selectRandomBook() else { return } book.title = "\(book.title)!!!" // OK: modifying a local copy }
-
17:39 - Passing data into and out of actors: classes
actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] func selectRandomBook() -> Book? { ... } } class Book { var title: String var authors: [Author] } func visit(_ account: LibraryAccount) async { guard var book = await account.selectRandomBook() else { return } book.title = "\(book.title)!!!" // Not OK: potential data race }
-
20:08 - Check Sendable by adding a conformance
struct Book: Sendable { var title: String var authors: [Author] }
-
20:43 - Propagate Sendable by adding a conditional conformance
struct Pair<T, U> { var first: T var second: U } extension Pair: Sendable where T: Sendable, U: Sendable { }
-
24:19 - Interacting with the main thread: Using a DispatchQueue
func checkedOut(_ booksOnLoan: [Book]) { booksView.checkedOutBooks = booksOnLoan } // Dispatching to the main queue is your responsibility. DispatchQueue.main.async { checkedOut(booksOnLoan) }
-
25:01 - Interacting with the main thread: The main actor
@MainActor func checkedOut(_ booksOnLoan: [Book]) { booksView.checkedOutBooks = booksOnLoan } // Swift ensures that this code is always run on the main thread. await checkedOut(booksOnLoan)
-
26:21 - Main actor types
@MainActor class MyViewController: UIViewController { func onPress(...) { ... } // implicitly @MainActor nonisolated func fetchLatestAndDisplay() async { ... } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。