大多数浏览器和
Developer App 均支持流媒体播放。
-
Xcode 16 的新功能
探索 Xcode 16 在效率和性能方面的最新改进。了解代码补齐、诊断工具和 Xcode 预览方面的增强功能。进一步了解构建版本方面的更新,并探索调试和 Instruments 方面的改进。
章节
- 0:00 - Introduction
- 0:29 - Updates in editing
- 0:33 - Code completion
- 1:01 - Adopting Swift 6 data-race safety guarantees
- 2:49 - Improvements to Previews
- 6:22 - Updates in builds
- 6:26 - Explicit modules
- 7:15 - Package resolution
- 7:15 - Package resolution
- 8:30 - What's new in debugging
- 8:35 - Build process and debugging
- 9:07 - Thread performance checker
- 9:23 - The organizer
- 12:16 - The RealityKit debugger
- 12:55 - Meet Swift Testing
- 18:42 - What's new in Instruments
- 19:44 - Meet the flame graph
- 21:38 - Wrap up
资源
相关视频
WWDC24
-
下载
嗨 我叫 Daisy 这是我的同事 Jake 我们将为大家 展示 Xcode 16 的一些新功能 很高兴在这里和大家见面 我们赶快开始吧 在 App 开发的每个阶段 都可以使用 Xcode 我们将介绍 Xcode 16 中都有哪些 新功能可助你编辑 Swift 代码、 运行构建版本、调试和测试代码 并最大限度地提高性能表现 我们先从编辑代码时 要用到的三项新功能开始
首先 代码补全功能可以提供 更全面的代码建议 这要归功于设备端编程模型 这个模型专门针对 Swift 和 Apple SDK 进行了训练
它使用周围的代码情境 比如函数名称和注释 以便更快地呈现你的想法
这多亏了 Apple 芯片 在 macOS Sequoia 上运行 Xcode 16 时可以使用这项功能
现在 我们来谈谈 Swift Swift 6 带来了一种新的语言模式 可以提供并发安全保障 它把通常只有在运行时 才会出现的数据争用 转变为编译时问题 这是一个可以提高代码 正确性和安全性的好方法 要充分利用这些保障 需要采用 Swift 6 语言模式 但现在 你可以先在 Xcode 16 中 为每种即将推出的语言功能 逐步启用警告 我来展示一下如何操作
我正在开发一款 名为“BOT-anist”的 App 其中的机器人可以 展示自己的种植技能 我想让这款 App 为 Swift 6 做好准备 我先导航到 “Build Settings”中的 “Swift Compiler - Upcoming Features”部分
在这里 我可以逐个 启用即将推出的编译器功能
首先将“Isolated Global Variables”设为“Yes”
当我构建时 Xcode 会在 问题导航器中显示一个新警告
提醒我 全局变量 logger 不符合并发安全要求
点按这个问题 会直接跳转到定义 logger 的位置
我知道 logger 不需要改变 因此我可以将它从 var 更改为 let 这样它就不会受到 任何数据争用的影响
通过采用即将推出的功能 你能够在迁移到 Swift 6 之前 发现并解决潜在的问题 以免这些警告 后续显示为错误
准备好后 可在“Build Settings” 中设置 Swift 语言版本
要进一步了解 请观看 “将 App 迁移到 Swift 6”
Xcode 16 在预览方面 也有重大改进
预览功能使你能够 快速迭代 UI 从而打造 出色的用户界面 我们推出了两个新 API 让代码编写变得更简单、 更容易重复利用 还能更好地 与你的模型集成
第一个是 Previewable 宏 Previewable 可以附加到 State 这类属性包装器上 便于你 在预览块中直接使用 无需再编写 包装器视图 我给大家展示 App 中的一个示例
这是 RobotFaceSelectorView 需要使用 Binding 关联到机器人面部
创建预览时 我可以直接 在预览的 body 中定义状态 然后附加 Previewable 宏 这个操作会让预览系统 在后台创建一个包装器视图 所以我就不需要再这样做了
然后我就可以添加我的视图
只需短短几行代码 就能直观呈现我的 UI!
第二个新的 API 是 PreviewModifier PreviewModifier 让你可以更轻松地 分享用于预览的环境或数据 这不仅可以减少重复代码 同时也能让预览系统缓存数据 为了更好地说明 我会切换到另一个视图 RobotNameSelectorView 需要 RobotNamer 才能发挥作用
这种类型会以异步方式从服务器 检索可能的机器人名称 不过 我们没有必要在 构建预览时反复连接服务器 我可以使用 PreviewModifier 为所有预览创建一个 RobotNamer 而无需连接我们的外部服务器
首先 我会定义一个 符合 PreviewModifier 的类型
这个协议有两个要求 首先 我使用 makeSharedContext 来载入和存储数据 这个方法是异步的 并且会引发异常 所以我可以异步载入数据 并处理错误 在本例中 我会使用本地文件 中的名称来创建 RobotNamer 这样就不需要连接外部服务器
关键在于 这种方法 针对所有相同类型的修饰符 只会调用一次 这是因为预览系统 已经为我缓存了数据
第二个要求是 body 方法 通过 body 方法 我可以使用共享情境来包装预览 为了保持一致性 我会使用 环境修饰符来传递 RobotNamer
定义了修饰符之后 我只需 使用特征就能将它提供给预览 但考虑到我会 频繁使用这个修饰符 我需要在 PreviewTrait 上 定义一个扩展 从而减少每个调用站点的代码 剩下的就只是创建预览了
这个案例相对简单 在某些情况下 PreviewModifier 特别有用 尤其是当你需要 在预览之间共享数据时 比如使用 SwiftData 的 ModelContainer 时
除了 API 我们今年 在预览的性能和基础架构方面 也实现了巨大的飞跃 启用了新的执行引擎后 预览比以往任何时候都要快!
得益于编译器、构建系统 和操作系统方面的技术改进 预览现在能够使用 项目通用的构建产物 即时重组程序 无需再制作单独的副本 预览是我们今年 在构建方面的一项改进 接下来由 Jake 给大家介绍 其他改进功能 谢谢 Daisy 我们来看看构建方面吧
Xcode 16 推出了显式模块 让你的构建版本更出色 这项功能提高了 并行性 改善了诊断功能 加快了调试速度 所有这一切都 无需更改一行代码
太不可思议了! 那么如何启用这项功能呢? 对于 C 语言和 Objective-C 默认启用显式模块功能 对于 Swift 你需要选择启用 我们来试一试
下面我直接跳到 “Build Settings”部分
启用“Explicitly Built Modules”
这样就可以开始构建了
嗯 随着 Xcode 16 推出 Swift 软件包 集成方面的改进功能 我不用等待软件包的解析完成 就能把构建加入队列 我喜欢这一点! 那么现在的流程究竟是怎样? 有了显式模块 Xcode 会把 每个编译单元的处理 分为三个单独的阶段:
扫描、构建模块 和最终构建原始代码
前两个阶段 如今在构建日志中 显示为“Scan dependencies” 和“Compile Clang module” 或“Compile Swift module”命令 以前会在源文件编译过程中 隐式执行这些操作 现在 你可以更详细地 细分构建 获得更好的并行性 而且如果构建因为模块问题而失败 错误信息也更加清晰
显式模块也会在 构建时间线中有所体现 这样你可以更轻松地查看 构建过程中的时间都花在了哪里 然后帮助你优化构建
要进一步了解 请观看: “揭秘显式构建的模块”
好的!我们已经编写并构建了 一些代码 现在可以进行调试了
但我先来讲一讲 构建流程促进调试改进的 几种方式 显式模块可加快调试速度 因为 lldb 在计算表达式时 可以重复使用构建的输出 而在针对 macOS Sequoia 或 iOS 18 的部署目标进行构建时 DWARF5 现在是 默认的调试符号格式 有了 DWARF5 dSYM 捆绑包更小 查找符号也更快速
在 Xcode 16 中运行时 线程性能检查器的功能更丰富了 除了查找主线程挂起 和优先级倒置之外 现在它还会显示磁盘写入过多 和 App 启动缓慢的诊断结果 从而帮助你改善 App 的性能
在 Organizer 中 会显示一个新的 App 启动诊断日志类别 如果你的 App 需要很长时间 才能在用户的设备上启动 Xcode 将显示 最缓慢的代码路径签名 便于你优先解决 影响最大的问题
如果你熟悉 磁盘写入诊断 那么你一定会喜欢这项 符合技术发展趋势的更新 现在 在 Organizer 中 你可以看到 相应问题带来的影响 在不同版本的 App 中 发生了怎样的变化
在“Disk Writes”视图下 可以看到一些签名旁边 显示了向上的箭头 这些箭头提供了不错的着手点 帮助你优先考虑 对用户影响最大的问题
在调试代码时 线程性能检查器 会将最严重的问题显示为 运行时问题 精确定位 需要重点关注的代码行 即使这个问题不会 在本地重现
在本例中 线程性能检查器 似乎标记了一个 影响用户体验的问题 它提示我们不应该在主线程上 载入这个视频素材资源
我要打开 Xcode 并设置 一个断点以便进行跟踪 下面我会打开 有问题的文件
点按空白区域设置断点 然后运行
好了 已经运行到了断点 为了便于追踪 这个调用是从哪里来的 我将在调试栏中启用 Unified Backtrace View
借助这种全新的可视化方式 我可以追踪调用堆栈 只需向下滚动 即可查看每一帧周围的代码
我还可以将鼠标悬停在变量上 这样就能查看它们的值
在 View body 方法中 这里有一个 “await”调用了这个代码
我们已经跟踪了这个调用 它来自 App 内部
这个视频播放器 初始化函数看起来像是异步的 并且是通过 SwiftUI 任务修饰符 调用的 但由于它从封闭的 SwiftUI 视图中 继承了 @MainActor 情境 所以在主线程上 执行了 I/O 操作! 我要将这个问题标记为 “nonisolated”来进行修复
这样应该就可以了
得益于 Unified Backtrace View 以及线程性能检查器 还有全新的 Organizer 报告功能 Xcode 可以帮助你专注于 最要紧的事项 另外 空间开发 也大大简化了 这多亏了全新的 RealityKit 调试器 现在只需点按一个按钮 你就可以采集 App 实体层次结构的快照 直接在 Xcode 内 以 3D 视图进行探索 这个调试器允许你查看 实体及其组件 还可以检查内置属性 和自定属性
要进一步了解 RealityKit 调试器 或现有的调试器 请观看: “探索 RealityKit 调试器” 和“运行、暂停、检查: 探索如何使用 LLDB 进行有效调试”
现在继续有请 Daisy 来讲一讲测试吧
Jake 你分享的调试 相关内容太棒了! 调试可以帮助大家 在开发的过程中 解决问题 测试也一样 可以有效地发现未来 迭代中可能出现的问题和性能下降
Swift Testing 是一种全新框架 它运用了 Swift 语言功能 从而使表达测试 变得更强大 更简洁 而且这些测试可以与 现有的 XCTest 配合使用 我来给大家展示一个 Swift Testing 的示例
我和团队正在为“BOT-anist” 添加一项新功能 机器人将获得 新的种植方式和动画状态 但我还没有把它们全部都测试一下 所以我们现在来添加新测试
首先 我将在测试文件夹中创建一个 新文件 将它命名为 PlantTests
要创建测试 我要先导入 Testing 框架和我们的 App 然后编写一个名为 “plantingRoses”的函数 我可以随心所欲地为这个函数命名 为了充分发挥 Swift Testing 的优势 只需添加 Test 宏即可
在我添加了 Test 宏之后 Xcode 就会知道这是个测试 然后会将它显示在导航器中
在这个测试中 我想确认玫瑰的 默认种植方式是嫁接 首先 我需要创建 一个类型为玫瑰的 Plant
然后我要创建 另一个类型为玫瑰的 Plant 但是把它的种植方式 指定为嫁接
太好了 这就是我想要的! 最后 我将使用 expect 宏 来验证这两个类型是相等的 这个宏可以接受任何布尔表达式 你可以用它来验证 字符串、浮点数或其他类型
现在让我们运行测试
哎呦很遗憾 测试失败了 测试结果表明 两朵玫瑰不一样 但是根据错误描述 它们看起来确实是一样的 二者显示的表情符号是相同的! 但是我不知道 其他属性是什么样
借助 Swift Testing 我可以查看关于每个值的更多信息 只需点按错误信息中的 “Show Details”就可以了 这样我就能更仔细地检查 哦 原来是种植方式不一样 还好我写了一个测试来检查默认值 这样就能快速修复问题了
.seedling 应该是 .graft
让我们再次运行测试 看看能否通过 可以使用快速操作来完成这项任务 为了显示快速操作窗口 我会按下快捷键 Command-Shift-A
然后输入“test again” 重新运行这个测试
太棒了 通过了! test 宏不仅能够 将函数标记为测试 它还接受多种特征 这些特征可以添加信息 或改变相应的行为 例如 你可以向测试函数提供 显示名称或参数
这个测试用来检查 我的机器人动画状态机
我想确保这些状态 可以过渡到 celebrate 为了避免编写 多个测试函数 我们为 test 宏 提供状态列表 同时只使用一个测试函数 将状态提取为参数
现在我们来运行这个参数化测试
在 Swift Testing 中 每个提供的 参数都会作为独立测试用例并行运行 导航器将单独显示 每个用例 让我知道哪些方案失败了 看起来 植物动画状态失败了 可能是我忘了将 .celebrate 添加为 有效的状态转换 我来修复一下
现在我可以只重新运行失败的测试 案例 点按它旁边的按钮就可以了
非常好!所有测试用例都通过了! 在 Xcode 中 Swift Testing 的 另一个好处是可以基于标签进行整理 Swift Testing 让我能够创建标签 对不同套件中的测试进行分组 我真的很想用这个功能 来执行这个操作 这样我就知道哪些测试与之相关 首先 我将展开 Tag 类型 把我的 自定标签 planting 包含进去
然后 我会将这个标签添加到 我想要标记的测试的 Test 宏 我要把它添加到这个测试中
还要添加到 plantingRoses
应用这个标签后 导航器中的 标签视图显示它们分组到了一起
如果我想运行标签中的所有测试 我可以点按它旁边的按钮 标签也可以用于 在测试计划中添加和排除测试
接下来 我会在导航器的顶部 选择“Edit Test Plan” 转到测试计划
新的种植和动画 功能仍在开发中 我不想因为这些测试 而导致 CI 出现任何不稳定 所以 我会将这个功能的标签 添加到排除标签列表 直到我准备好启用它
要进一步了解 Swift Testing 请观看“了解 Swift Testing” 和“利用 Swift Testing 进一步优化测试”
我们已经调试并测试了代码 测试运行得很快 但 App 并没有 我让 Jake 看一看 谢谢 Daisy 我们刚刚使用测试 验证了 App 能否正常运行 但是当我运行这款 App 时 我注意到 启动用时 远超我最初的预期
要诊断性能问题 Instruments 就是最好的工具 可以直接从 Xcode 的 Profile 操作中访问 我可以查看 App 启动过程的跟踪记录 这个记录由 Daisy 使用 “Time Profiler”Instrument 生成 这个 Instrument 工具用可视化 方式显示代码中 CPU 的使用情况 让我们可以衡量 App 运行所需的时间 现在我们来看一看
哇!占用了大量 CPU 而且初始启动时 挂起时间很长 让我们来看看怎样对此进行优化 为方便了解为什么会出现这种情况 首先 我把检查范围设置为 挂起时间间隔
这样我就能只查看跟踪记录的一部分 现在我们来分析一下数据
为了帮助缩小问题的范围 我会使用 Instruments 16 全新的火焰图 可以从转跳栏激活这一功能 火焰图可以简要概括 跟踪记录的执行情况 便于我们 迅速发现问题 执行间隔按照它们在跟踪中 所占时间的百分比进行权重计算 而且间隔 从左到右排序 也就是说 图表的左边部分 始终会显示 执行次数最多的代码 我们来看看 我的程序在执行什么
看起来这个载入函数 几乎是用掉了所有执行时间
而且它从 SwiftUI View body 中被调用 这看起来不太对
右键点按这个框架 选择“Reveal in Xcode” 这样就能直接看到代码了
啊 找到问题所在了 我们正在循环串行载入 一组素材 并且是在主线程中执行这个操作 这可不好 要解决这个问题 我会利用 任务群组进行并行载入 这样操作还会将这个工作 移到所属的后台去执行
现在我要进行重建 在 Instruments 中重新运行 Time Profile 看看是否有帮助
很好!启动进行得非常快 在火焰图中可以看到 素材的载入现在被分发到 多个后台线程 不会再堵塞主线程
火焰图适用于所有使用 调用树的 Instrument 这种查看调用堆栈的可视化方法 非常便于查找问题 同时也能让你轻松地 定期对 App 进行性能分析
以上是 Xcode 16 中的一些 令人激动的新功能 要进一步了解这些内容或改进功能 比如更快的 UI 预览 强化版 C++ 运行时功能、 优化的本地化工作流程等 请访问 developer.apple.com 查看我们的发布说明 当然 最好还是 下载 Xcode 16 亲自试一试! 感谢观看 祝你在 WWDC 度过美妙的时光!
-
-
3:37 - Inline State within Preview
#Preview { @Previewable @State var currentFace = RobotFace.heart }
-
3:45 - View using Inline State
RobotFaceSelectorView(currentFace: $currentFace)
-
3:53 - Complete Preview using Previewable
#Preview { @Previewable @State var currentFace = RobotFace.heart RobotFaceSelectorView(currentFace: $currentFace) }
-
4:40 - Type Conforming to PreviewModifier
struct SampleRobotNamer: PreviewModifier { typealias Context = RobotNamer static func makeSharedContext() async throws -> Context { let url = URL(fileURLWithPath: "/tmp/local_names.txt") return try await RobotNamer(url: url) } func body(content: Content, context: Context) -> some View { content.environment(context) } }
-
5:29 - Extension on PreviewTrait
extension PreviewTrait where T == Preview.ViewTraits { @MainActor static var sampleNamer: Self = .modifier(SampleRobotNamer()) }
-
5:38 - Preview using created PreviewModifier
#Preview(traits: .sampleNamer) { RobotNameSelectorView() }
-
10:26 - AVPlayer Creation
struct BOTanistAVPlayer { func player(url: URL) throws -> AVPlayer { let player = AVPlayer(url: url) return player } }
-
11:28 - AVPlayer Call Site
self.player = try? await robotVideoAVPlayer()
-
11:57 - AVPlayer Initialization
private nonisolated func robotVideoAVPlayer() async throws -> AVPlayer? { guard let url = Bundle.main.url(forResource: RobotVideo.resource, withExtension: RobotVideo.ext) else { throw BOTanistAppError.videoNotFound(forResource: RobotVideo.resource, withExtension: RobotVideo.ext) } let avPlayer = BOTanistAVPlayer() let player = try avPlayer.player(url: url) return player }
-
13:42 - Initial Test Scaffolding
import Testing @testable import BOTanist // When using the default init Plant(type:) make sure the planting style is graft @Test func plantingRoses() { // First create the two Plant structs // Verify with #expect }
-
14:36 - Complete Test
import Testing @testable import BOTanist // When using the default init Plant(type:) make sure the planting style is graft @Test func plantingRoses() { // First create the two Plant structs let plant = Plant(type: .rose) let expected = Plant(type: .rose, style: .graft) // Verify with #expect #expect(plant == expected) }
-
17:35 - Custom Tag
extension Tag { @Tag static var planting: Self }
-
17:42 - Tag Usage in @Test
.tags(.planting)
-
20:37 - Slow Asset Loading
for asset in allAssets { asset.load() }
-
20:54 - Fast Asset Loading
await withDiscardingTaskGroup { group in for asset in allAssets { group.addTask { asset.load() } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。