大多数浏览器和
Developer App 均支持流媒体播放。
-
Xcode 基础知识
编辑、调试、提交、重复。探索 Xcode 中的工具套件,助你在开发 App 时进行快速迭代。了解有助于优化和提升开发工作流程的技巧与窍门。
章节
- 0:00 - Introduction
- 1:31 - Find the right content
- 1:45 - Filter navigators
- 3:05 - The Find navigator
- 5:43 - Move between files
- 5:47 - The tab bar
- 7:41 - The jump bar
- 8:38 - Tricks for creating new files
- 9:34 - Warnings and error annotations
- 10:42 - Using bookmarks
- 10:56 - Using Mark comments
- 11:15 - Leverage shortcuts
- 11:22 - Open Quickly
- 12:12 - Useful commands and shortcuts
- 14:19 - Code completion
- 15:10 - VIM mode
- 15:25 - Common emacs commands
- 15:43 - Get the most out of git
- 15:47 - Show the last change for a line
- 16:07 - Changes navigator
- 16:55 - Debugging
- 17:21 - Setting breakpoints
- 21:40 - Using the console
- 23:01 - Testing
- 23:51 - Test Navigator
- 26:09 - Running tests
- 27:42 - Using Test plans
- 28:38 - Code coverage
- 29:36 - Explore the Test report
- 30:23 - Distributing your app
- 30:48 - TestFlight
- 31:26 - Archiving
- 33:26 - Explore the Organizer
- 35:41 - Wrap up
资源
- Forum: Developer Tools & Services
- Including notes for testers with a beta release of your app
- Testing
- Xcode updates
相关视频
WWDC24
WWDC23
WWDC22
Tech Talks
-
下载
各位软件工程师 大家好! 我叫 Myke 是 Xcode 的项目经理 我叫 Cheech 是 Xcode 团队的设计师 无论你是精通 Apple SDK 的老手 还是刚接触我们平台的新手开发者 我们将要介绍的基础知识都可以 帮助你使用 Xcode 高效工作 让你能够在整个开发周期中 提高速度、明确思路并收获快乐 我们每天都会编辑、调试、 测试、提交 周而复始 在新项目中 这个周期很快 涉及的研究工作也很少 但是 随着项目和团队不断壮大 或者当我们加入新团队时 平衡状态就会发生变化 如何着手处理问题这件事 变成了不亚于问题本身的问题 添加功能和修复错误 需要进行更多研究 这样我们才能在恰当的地方 做出恰当的更改 好消息是什么? Xcode 提供了大量功能 来帮助你查找 进行下一次更改所需的代码 我将介绍如何提高编辑速度 然后 Myke 将为大家演示 如何对更改进行调试和测试 准备好将你的 App 分享给受众之后 就到了要进行分发的时候 Xcode 中提供了非常丰富的功能 其中包括许多强大的功能 你将了解如何利用这些功能 在不同文件之间轻松切换 以找到要编辑的相关代码 我们将了解 Xcode 最强大的键盘命令 我还将介绍一些让你能够 充分利用 git 的窍门 我们先来了解一下 如何找到相关内容 如果你是第一次打开 Xcode 你可能会心想 “哇 东西可真多啊” 没错 就是这样 实现开发周期快速迭代 所需的一切资源 都已嵌入到 Xcode 中 在界面的左侧 每个导航器都能带你 从不同的角度了解你的项目 在这个视图中 项目导航器 处于选中状态 显示了项目的文件层次结构 如果你在开发流程中不断创建新文件 不断编写代码 然后发现 导航器变得杂乱不堪 那么 建议先添加一两个文件夹 然后尝试在底栏中 输入文件名进行查找 还记得我刚才提到的强大功能吗? 事实上 许多导航器中 都有专用的筛选器 如果我开始输入 并且我的文本 与其中某个筛选器相匹配 输入内容上方会显示一个弹出式菜单 提供额外的功能 比如按目标名称筛选 显示在底栏右侧的图标 也会根据导航器而变化 点按最右侧的图标 可通过 git 状态筛选文件 这样就可以轻松返回到 即将提交的文件 同时在整个项目的上下文中 显示这些文件 如果你在 Xcode 中 提交代码时遇到问题 可以尝试使用这个筛选器 在提交之前轻松检查更改 如果你确实知道文件名 筛选功能就会非常好用 但如果你想不起文件名了 该怎么做? 我知道 我也没有遇到过这种情况 我们只是假设一下 在这种情况下 建议使用 Xcode 的 Find 导航器 按下 Command-Shift-F 即可 借助 Find 导航器 你可以 在整个项目中进行搜索 有时只能搜索到几个匹配项 你所需要的全部结果只有这些 还有些时候 你搜索的内容 在整个项目中随处可见 因此需要缩小搜索结果的范围 这种情况对我来说就是家常便饭 幸好 Xcode 非常善解人意 你可以使用一些工具 来快速找到所需的结果 如果你猜到了底栏 那么你猜对了 和项目导航器一样 你可以使用底栏来缩小搜索范围 请尝试在匹配的行中筛选其他字词 或者按文件名进行筛选 以查看这个文件中的结果 但如果你不太确定要查找什么 可以通过一些巧妙的方式 与导航器中的内容交互 要在查看任何文本匹配项之前 先快速浏览一下文件名 我会按住 Command 键 同时点按 文件名左侧的展开箭头 收起所有同级 这项操作适用于 Xcode 中的所有大纲视图 现在 我决定不对前 3 个文件中的 “padding”进行任何更改 因此 我想将这些文件从搜索中移除 以便专注于相关性更高的文件 首先 选中 3 个文件 然后按下 Delete 键将这些文件移除 别担心 我并没有删除那些代码 这些文件仍然存在 只是对当前查询隐藏 现在 我可以专注于我关心的结果了 如果你的查询和我一样 有很多匹配项 但你只关心某些组中的文件 请使用搜索栏下方的菜单 来突出搜索中的重点 可以选取包含当前所编辑文件 的任意组 选取“Custom Scopes…” 可选择其他组 甚至可以选择多个组 如果你发现自己经常 使用同一个搜索范围 可以存储这个搜索范围 然后它会 显示在导航器的初始菜单中 点按搜索栏上方的“Text”一词后 可以从打开的菜单中筛选 Xcode 的符号索引 “Descendant Types”非常适合 用于大致了解类层次结构 你可曾遇到过这种情况 想要将某些 内容拷贝粘贴到 Find 搜索栏中 却发现之前拷贝到剪贴板中的一些重要信息 现在已不见了踪影? 幸好 macOS 提供了一个解决方案 选中任意文本后 按下 Command-E 这些文本就会直接显示在 Find 搜索栏中 而剪贴板原封不动 这项操作适用于任何 macOS App 很妙吧? 好的 我们刚才介绍了如何 通过筛选和搜索查找相关内容 接下来 我想介绍一些 在不同文件之间切换的便捷方式 首先是标签栏 它位于靠近源代码编辑器顶部的位置 在工具栏下方 可以让你轻松切换不同文件 我们今天已经打开过很多文件 但标签栏中只打开了 4 个标签 为什么会这样? 事实上 Xcode 有 2 种类型的标签 一类是永久性标签 表示我以显式方式表现出兴趣的文件 比如编辑过代码的文件 另一类是隐式标签 Xcode 会创建这类标签来表示 我只是短暂浏览过的文件 我退出文件后 这种标签就会消失 隐式标签与其他标签很容易区分 因为隐式标签的标题采用的是斜体 如果你想让某个标签成为永久性标签 但又不想进行编辑 可以从上下文菜单中 选取“Keep Open” 或者双击这个标签 完成任务后 是否想要 一次性关闭一堆标签? 不必逐个关闭标签 试试按住 Option 键同时点按 一个标签的关闭按钮 就能如愿以偿地 把所有其他标签一起关闭 在标签栏左侧 后退按钮和前进按钮具有 正常的后退和前进按钮功能 但如果长按其中一个按钮 比如后退按钮 就可以查看完整历史记录 无需点按二十来下按钮 就能立即跳转到想要后退到的位置 后退按钮左侧 是相关文件菜单 它的名称不言自明 菜单中有一个 Recent Files 列表 此外 还会显示不同类型的符号关系 具体取决于相应的文本 例如当前类的子类 或是当前函数的调用方 在这里 你可以探索 各种各样的功能 标签栏右侧的三个按钮 用于配置编辑器 UI 中间的按钮可控制编辑器布局 你可以添加 SwiftUI 预览、 编辑器缩略图 以及 blame 或 code coverage 等行附属项 有时导航器中显示了重要信息 需要保持不变 还记得刚才的 Find 查询吗? 但你仍然想要在相关文件之间跳转 你可以在标签栏中翻找 也可以使用转跳栏 转跳栏位于标签栏正下方 显示了当前文件的路径 这个路径中的每一步都是交互式的 点按某个项目可查看相邻文件 这些列表可能会很长 如果你和我一样 那么浏览大型菜单时 你可能会觉得晕头转向 开始输入 菜单顶部 就会显示筛选栏位 没错 你可以对菜单进行筛选 Xcode 中的许多菜单 都提供了这项功能 不妨试一下 要从项目导航器中的转跳栏查找项目 可以按下 Command-Shift-J 如果你想在当前文件附近 创建新文件 不妨试试这项功能 在 Xcode 中 你可以 通过多种方式创建新文件 右键点按任意位置 可以跳过模板选择器来创建空白文件 如果你想沿用现有文件的格式 来创建新文件 可以通过拷贝和粘贴 或是复制等经典操作来完成 我个人最喜欢的方法是 按住 Option 键 然后点按并拖移项目以进行拷贝 我会对各种项目使用 按住 Option 键同时拖移的操作 有时文件太大了 可以通过 Command-X 将部分内容 剪切到剪贴板 然后右键点按导航器中的任意位置 按住 Option 键 一些菜单项会发生变化 选取“New File from Clipboard” 使用剪贴板中的内容创建新文件 按住 Option 键 显示备用菜单选项 这是 Mac 上的另一项强大功能 很巧妙 对吧? 也可以将内容 直接粘贴到导航栏中 Xcode 会为你创建新文件 如果你的代码出错了 当然 这种情况很少见 Xcode 会用警告或错误信息 为相应的行添加注释 这些注释是交互式的 如果注释中有些信息没有显示出来 点按注释可以将它打开 并显示其余信息 如果一行中存在多个问题 可以点按注释将它打开 如果编译器提供了解决办法 那么你应该已经猜到了 可以点按注释将它打开 然后点按 Fix 应用这个解决办法
解决办法会针对编写代码时的 语法错误提供建议 如果问题变成灰色 就说明自从这个问题上次刷新以来 你对这个文件进行过更改 如果问题不见了 那就没有问题了 但如果问题仍然存在呢? 你就必须重新尝试 你实际上可以插入自己的警告 通过这种方式来进行任务管理 在编辑器中输入这个标记: #warning 和一条信息
如果你有过 238 条短信未读的经历 你肯定知道这种感觉 错误注释可能非常有用 但是我个人更喜欢干净清爽的界面 在这种情况下 书签可以发挥作用 右键点按编辑器中的任意位置 可以为一行代码添加书签 书签是个人进行任务管理的绝佳方式 此外 在书签导航器中进行清点核对 也非常方便 想要添加持久化注释? 可以尝试在文件中添加 MARK 注释 它们会充当相应部分的标题 并显示在缩略图中 和编辑器内容转跳栏分段中 哇 你可以通过许多方式 在不同文件之间切换 希望你学到了新东西 目前为止 我已经 提到了很多键盘命令 但其实 Xcode 还有更多 快捷键等你来探索利用 Open Quickly 是 Xcode 中 最接近“瞬间移动”的功能 提供了 A 点和 B 点之间 最快捷的路径 只需在任意位置按下 Command-Shift-O 然后输入文件名或符号名称 中的部分文本 Open Quickly 功能就会立即 显示一个目标位置列表 这项功能采用 类似代码补全的匹配规则 因此 只需输入目标位置名称中 独有的文本 就能立即前往目标位置 如果在查询中加入斜线 就可以匹配文件路径 而不是文件名 使用冒号加行号作为 查询的结尾 可前往特定的行 你可能希望在新的 拆分视图中打开文件 按住 Option 键 同时按下 Return 键 这样就能并排查看两个文件了 还可以查看 Xcode 的导航设置 对修饰符行为进行自定 Xcode 提供了超多功能强大的命令 以至于用来新建快捷键的 键盘按键都快不够用了 我已经记住了自己最喜欢的快捷键 但对于其余的命令 我不会在菜单中筛选 而是会使用快速操作 按下 Command-Shift-A 即可打开快速操作 你可以用自然语言 搜索 Xcode 的命令 你可以浏览和自定 Xcode 的许多独有命令 只需找到 Xcode 的 Settings 面板 然后打开 Key Bindings 标签 下面介绍我们团队最常用的一些命令 “Jump to Definition” 可以通过按住 Command 同时点按来调用 这个命令可以带你前往 函数或类型的定义所在的位置 按住 Option 键同时点按 可以调用“Show Quick Help” 这样一来 如果你点按了符号 就能查看相关文档 如果点按了 Swift 变量 则可以查看推断类型 “Edit All In Scope” 可通过在选中文本后按下 Command-Control-E 来调用 这个命令可以重命名符号以及 出现在当前文件中的所有同一符号 右键点按后选择 “Show Callers...” 可查看当前函数的所有调用方 如果某个函数调用没有分行 不妨试试通过“Control-M” 将这段代码改为多行格式 想要找到另一半圆括号、 方括号或引号? 双击相应符号的一边 就能跳转到另一边 你可以使用 Option 键加箭头键 来逐词移动光标 也可以使用 Control 键加箭头键 来逐子词移动光标 如果你熟悉按 Home 和 End 移到行首和行尾的操作 那么 Command 键加左箭头 或右箭头键会非常有用 使用源代码编辑器的 多光标编辑功能时 这些文本移动命令必不可少 需要创建重复的 switch 语句或构造器 对多行代码执行同一操作? 按住 Control-Shift 然后点按要插入多个光标的位置 就能一次性创建多个语句
如果需要在多个位置使用相似的代码 可以创建一个模板 插入与代码补齐功能中同样的占位符 使用这些字符包围相应的文本 Xcode 的代码补齐功能 可以帮助你补齐代码 甚至在你只记得 几个词的情况下也能补齐 在 Xcode 16 中 预测性补齐功能 可以根据周围的代码给出 完整语句和方法的建议 这些补齐建议 会以内联方式显示在代码中 你可以按下 Tab 键 接受显示的建议 也可以按住 Option 键 展开整个预测 按下 Option-Tab 键 接受完整建议 下面介绍一个专业技巧 注释和变量名称 有助于为预测提供信息 你的描述越清晰 预测就会越准确 补齐窗口在很小的空间内 融入了丰富的功能 你可以在窗口底部看到 方法的完整类型签名 按住 Option 键 选取代码补齐时 按下 Enter 键可接受所有参数 如果你以前使用过 vim 你就会知道退出这种模式并不容易 在 Xcode 的 Editor 菜单中 你可以 在进入和退出 vim 模式间轻松切换 Xcode 16 还支持 将 vim 的 repeat 命令 作为另一种多光标编辑方式 你可能喜欢以不使用箭头键的方式 在一行代码中来回移动光标 Xcode 还支持所有原生 macOS 文本编辑交互 其中包括支持许多 基本的 emacs 命令 比如 Control-A、E、P 和 N 以及许多其他的命令 接下来 我们看看一些 充分利用 git 的简单窍门 假设你正在修复一个错误 想要进一步了解 这个错误是怎么来的 可以右键点按 然后尝试选择 “Show Last Change for Line” 就能简要了解这一行的提交情况 这项功能就像一个 有针对性的 blame 版本 要做好心理准备 因为 你可能会发现问题是由你引入的 当你准备好在提交代码之前 检查你做出的所有更改时 在 Changes 导航器中 预览即将提交的代码 先花点时间来评估你取得的进展 然后再暂存更改、提交代码 放心地去吃午餐 我们来回顾一下如何 在 Xcode 中加快编辑速度 我们讨论了找到相关内容的 两种方法: 在导航器中筛选内容 和使用 Find 导航器 我们了解了借助代码注释 使用标签栏和转跳栏 在不同文件之间进行切换的新方法 别忘了利用 Xcode 的众多快捷键 要善用 Open Quickly 和快速操作 最后 我们讨论了 使用 git 的几个窍门 接下来 有请 Myke 来谈谈如何进行调试 谢谢 Cheech! 进行调试的目的是 找出需要更改的相关代码 你可能确切知道哪一行代码存在问题 但常常并不清楚其中的缘由 代码可能运行了数百次都没问题 但这一次却突然出错了 有时 看似无伤大雅的错误 也许终会酿成大错 在很久以后导致运行失败 我想介绍一些使用断点的超棒窍门 然后介绍 如何大大提高 print 语句调试的成效 首先 我要介绍断点的一些功能 这些功能可以帮助你查明问题 尤其是高流量代码中的问题 在比较简单的代码中 只需点按行号添加断点 程序就会在运行到这一行时停止 如果过于频繁地到达断点 以至于断点变得不实用 不妨尝试使用下面介绍的这些技巧 你可以使用一前一后两个断点 假设你有一个高频执行的函数 持续不断地运行 这个函数还会在用户点按 App 中的某个按钮后运行 而且只有这一种调用方式 会出现问题 在这种情况下 你可以 在按钮处理程序中添加一个断点 然后在高频执行的函数中 添加第二个断点 第二次点按高频执行函数中的断点 将这个断点停用 运行程序时 到达 按钮处理程序中的断点后 重新启用高频执行的函数 中的断点 然后继续运行 现在 你可以在正确的状态下 到达第二个高流量断点了 使用完这些断点后 需要将它们 拖移出去 以便从代码中移除 如果你有一项操作意外运行失败 并抛出 Swift 错误 你可能会很难找出错误的源头 你可以在断点导航器中添加一个 “Swift Error Breakpoint” 从而停在抛出错误的位置 而不是捕获错误的位置 你的 App 会在抛出任何错误 的位置立即停止运行 如果你的应用程序经常 抛出和捕获预期的错误 就说明这个断点的活跃度 可能有点偏高 可以考虑使用刚才的示例中 启用断点的技巧 来确定 Swift 错误断点的作用域 有时候 某个高频执行函数的 相关信息已经足够多 因此我可以 从噪声中筛选出值得关注的情况 例如 我知道这个函数只有在 采用特定连接类型时才会运行失败 所以 我就可以 只针对这种情况设置断点 方法是双击断点进行编辑 然后添加与这个连接类型有关的条件 现在 断点只会 在这个条件为 true 时停止 还有些时候 你在进行调试时可能 只想进行日志记录 但不想停止运行 插入 log 语句 然后重新构建 思考如何解决问题 利用断点编辑器 你还可以 添加在到达断点时运行的 调试器表达式 添加 print 表达式 并将断点设置为自动继续运行 现在 无需重新构建 就能收集临时日志了
你可曾遇到过这种情况: 花费几分钟时间来设置调试场景 结果多走了一步 只好从头再来 重新设置? 接下来 我要介绍一下如何使用 刚才用于记录日志的调试器命令 来找出导致程序出现问题的原因 我们无需从头开始 就可以找出根本原因 例如 根据预期 我的函数 应该在我对这个 guard 子句 使用 step over 方法时执行 但它没有执行 我不知道这是为什么 我可以对表达式进行追溯性拆分 在调试器中使用“p session” 等命令来评估表达式的各个部分 甚至还可以 评估 guard 条件子句 找出导致返回意外结果的原因 你甚至可以将调试器 当作预知未来的“水晶球” 在采取下一步行动之前 先完成这项操作 然后再决定是要使用 step in 还是 step over 方法 你知道吗 甚至可以 将绿色程序计数器向后拖移? 它会尝试重新执行 之前执行过的表达式 副作用将无法撤销 所以程序可能会处于一种奇怪的状态 但是如果另一种可选方案是 点按 Stop 按钮 然后重新启动调试会话 何不一试试向后拖移的方法呢? 这样能够节省不少时间 最后 如果你正在调试某个问题 并且设置了一组断点 但中途想要 在不中断的情况下回到开头 可以使用调试栏中的断点按钮 来停用所有断点 继续进行调试 重新启用断点 然后重新触发问题 有时你需要对同一个问题 进行多次调试 两次、三次 说实话 有时需要 30 次调试 如果程序崩溃 你每次逐步完成崩溃诊断时 都必须重新启动程序 这非常令人困扰 尤其是在你必须多等几秒钟 才能构建和运行 你根本没有更改的应用程序时 按下 Command-Control-R 可选择“Run Without Building” 从而跳过整个构建步骤 并立即返回到调试 即使你已经开始更改 App 但想要再对旧代码进行一次调试 如果自从上一次会话以来 你没有进行过重新构建 那么也可以使用这项功能 当你在调试会话中层层深入 或是对不熟悉的代码进行调试时 可能会难以确定自己所处的位置 在 Xcode 16 的编辑器中 你可以查看完整的回溯栈跟踪 整个项目中的函数 全部整合到一个编辑器中 因此 你可以非常方便地大致了解 自己是如何走到当前位置的 你可以在调试栏中启用这个查看模式 它位于内存调试器 和视图调试器的控制项旁边 你现在有没有使用“print” 语句进行调试? 它们非常方便好用 我也经常使用 但是 它们可能很快就会变得笨重 尤其在你需要与整个团队 共享调试控制台输出的情况下 你可能会注意到 我在 print 语句中使用了一些宏 你也可以使用其中几个宏 例如使用 fileID 来获取缩短的文件路径 或者也可以 使用 #function 获取当前所处函数的名称 如需了解更多的宏 可以查看相关文档 不过 接下来我们要清理一下 不再使用 print 语句 而是考虑改为使用 `os_log` 这样可以为你设置的每条信息 指定调试级别 所以就不再需要那些宏了 然后在运行时 可通过搜索文本进行筛选 也可以只筛选出你库中的信息 你可以启用元数据以显示 Type 例如 error、info 或 debug 还可以显示 Timestamp 和 Library 我们之所以不再需要 在日志信息中使用那些宏 是因为只需点按前往按钮 就能直接跳转到源代码中 发来日志信息的那一行 这是无法单靠 print 语句实现的! 要进一步了解如何进行调试 请观看今年的 “运行、暂停、检查”讲座 要深入了解控制台提供的 各种出色功能 以及 os_log 附带的所有元数据 请观看 “使用结构化日志记录进行调试” 要进一步了解调试器 请观看“使用 LLDB 对 Swift 调试进行调试” 现在 我们已经消除了错误 接下来谈谈测试 测试非常重要 因为通过测试 可以在发布之前发现错误 如果确实发现了错误 添加测试用例可以防止错误再次出现 尤其是随着代码库变得 越来越大、越来越复杂 测试对于让你享受 写代码的乐趣至关重要 如果我想运行项目中的所有测试 我会按下 Command-U 但我通常希望一次只对一个测试 进行快速迭代 要进行这个操作 点按这个测试函数对应的菱形图标 或者点按层次结构中的 任意上级位置 进行系列测试
你可以进行许多测试操作 以上操作仅仅是个开始 接下来 我们深入了解一下 有助于高效完成测试的更多技巧 对于 Xcode 本身 我们实现了出色的 CI 覆盖 每个拉取请求 都通过 Xcode Cloud 运行 从而确保了在集成之前通过测试 测试导航器可通过 Command-6 打开 其中显示了我的所有测试 如果我针对 App 的不同部分 指定了多个测试计划 可以只显示当前计划中的测试 方法是使用筛选条件 “Only Included Tests”进行筛选 根据测试套件的大小 可能只需滚动浏览 并运行相关测试就足够了 但如果你像我们所有人一样 需要运行许多相对复杂的出色测试 你就会希望能够更加专注 如果你能够根据标题确定相关测试 就可以使用文本进行筛选 或者 如果使用 Swift Testing 也可以按标签进行筛选 确定了相应的子集后 只需选择这些测试 然后使用 上下文菜单来运行这组针对性测试 运行这些测试后 它们会出现相应的状态 如果我只想关注失败结果 也可以通过一个筛选条件来实现 这些测试会在我完成相应的修复后 自动从列表中消失 这项功能很棒 也让人很有成就感 就像调试我的 App 一样 我经常需要运行第二次测试 有时甚至要进行到第 30 次 没有测试能够打败我们 31 次! 我可能刚才已经离开了 最初的测试方法界面 并通过几次搜索或通过调试堆栈 深入到应用程序的核心中 在这种情况下不必回去 找到菱形图标来重新运行测试 而是随时可以再次运行 之前运行过的测试 只需按下 Command-Control-Option-G 与运行一样 我经常在不更改代码的情况下 重新启动调试会话 以便从另一个角度看待问题 可以选择“Test Without Building” 或按下 Command-Control-U 除了令人欣慰的绿色勾号图标 以及用意虽好但难免 令人失望的红色 X 图标 你在 Xcode 中还可能看到 另外几种测试状态 我想介绍一下如何使用它们 有时候 测试中出现的问题 是无法修复的 如果你热衷于测试驱动的开发 那么在 API 能够正常运行之前 你可能已经编写了测试 可以将这些测试标记为预期失败 它们会显示带有 X 的灰色图标 有时 你引入了一个回归错误 但准备暂时不去管它 尽管像我这样的项目经理 会劝你不要这样做 可以将这类测试标记为跳过 表示这种状态的 是带有箭头的灰色图标 对于套件中的测试 如果这些测试得出的结果不一致 你会看到这种表示混合状态的图标 当你准备好让所有这些图标 都变回绿色时 只需使用导航器筛选条件 来筛选这些测试状态 以便专注处理相关测试 运用我们刚才讨论的调试技巧 为发布做好周全准备 我已经介绍了如何运行单个测试 但是你也可以通过许多 其他的方法来运行测试 如果你有一个测试通常都能通过 但有时会失败 那就说明可能存在争用情况 或是另外一些不确定行为 在上下文菜单中选择 “Run 'testStarRating()' Repeatedly...” 可以让测试运行固定的次数 也可以让测试反复运行 直到失败为止 不妨考虑在测试中添加日志记录 以便在测试最终失败后 可以浏览日志了解原因 你可以在命令行中 使用“xcodebuild test” 来运行测试 只需指定方案、测试计划或单个测试 这个方法非常适合 与 git bisect 等工具配合使用 以找出回归错误是在什么时候引入的 当你准备好在持续集成环境中 运行测试 以减轻本地 Mac 电脑的 工作负载时 你可以使用开发者账户附带的 Xcode Cloud 服务 每月可以免费使用 25 个计算小时 你可以配置工作流程 以便在推送到特定分支后 立即开始在云端测试 并且直接在 Xcode 中查看结果 甚至可以将测试配置为 在测试通过后 直接将 App 提交至 TestFlight 或 App Store Connect Xcode Cloud 还能确保 安全性和私密性 对静态数据进行加密 并通过双重认证保护访问权限 只有在进行构建时 才会使用你的源代码 一完成构建 构建环境 就会被立即销毁 要开始使用 Xcode Cloud 请观看“在 Xcode Cloud 中 创建实用的工作流程” 然后 观看“充分利用 Xcode Cloud”了解更多进阶技巧 要了解如何写出更出色的测试 请观看“为 Xcode Cloud 编写快速且可靠的测试” 测试计划定义了如何创建测试分组 以便只在指定的时机运行指定的测试 随着项目不断壮大 你可能需要 采用几种不同的方案进行测试 或是创建不同的测试分组 以便只运行快速单元测试 然后在提交之前 使用另一个分组进行全面测试 这时候 测试计划就能发挥作用了 每个方案中可以包含多个测试计划 而一个测试计划可以跨越多个方案 新项目会附带有 已经为你创建的测试计划 首先 通过 Product > Test Plan 菜单编辑测试计划 先选取要包含的目标 例如将单元测试 和 UI 测试都包含进来 然后从这些目标中选取 这个测试计划要包含的测试 还可以将测试计划添加到多个方案 首先 选择方案 然后依次点按加号按钮 和“Add existing Test Plan…” 要运行测试计划 需要 从 Product > Test Plan 选中计划 然后选取 Product > Test 或按下 Command-U 来运行 通过代码覆盖或测试覆盖功能 你可以确定在运行测试时 执行的代码在代码库中所占的比例 编写测试对于发现问题非常重要 你可以打开代码覆盖功能 来评估测试的成效 并了解现有测试是否能覆盖新代码 首先 从 Editor 菜单中 启用 Code Coverage 然后运行测试 这样 Xcode 就可以确定 你的代码覆盖情况如何 所以直到首次运行时 你才会得到结果 运行测试后 可以在编辑器右侧 看到一个数字 这是代码块在测试过程中的执行次数 0 表示代码从未执行 因此要么测试出现了间断 要么你的 App 可能 根本不会执行这段代码 这个代码块在测试过程中 执行了 5 次 你还可以通过 Report 导航器 或按下 Command-9 来概览所有代码覆盖情况 这里会显示每个文件 和每个函数的代码覆盖率 以便有针对性地加以改进 这个可以显示代码覆盖率的标签 名为 Test Report 关于测试的运行情况 以及测试失败时出现的错误 你可以获得深层次的数据 我们来点按刚才运行的测试 查看测试内容摘要 也可以点按 Tests 项目 仅查看测试结果 在这个视图或摘要视图中 双击失败的测试 可以查看具体的情况 你可以并排查看 测试执行过程中的事件序列 以及屏幕录像 这项功能非常实用 你可以了解 App 在进行测试时的状态 以便准确找出问题所在 在底部的时间线中 可以看到失败发生的确切时间 “Apple Developer”App 中 有许多关于测试的精彩讲座 建议首先查看文档 “在 Xcode 中测试你的 App” 然后观看今年的讲座 “了解 Swift Testing” 好的 现在你已经 写好了 App 的代码 修复了所有错误 并进行了全面测试 时机已经成熟 可以让全世界 看到你的作品了 或者至少让 Beta 版测试员看到 我们即将举行产品发布会 是时候正式发布 1.0 构建版本了 我要介绍一下 如何使用 TestFlight 向 Beta 版测试员分发 App 对构建版本产品进行归档 然后使用内置的 Xcode Organizer 来获取更多洞察信息 从而深入了解你的 App 在面向所有人发布 App 之前 你需要让测试更上一层楼 由 Beta 版测试员 实际使用 App 并提供反馈 即便是最出色的测试 也无法替代现实中的使用
付费开发者账户包含了 TestFlight 让你能够向多达 1 万名 Beta 版测试员分发 App 你可以通过发送电子邮件或通过 在社交媒体上发布链接来发出邀请 Beta 版测试员会在他们的设备上 自动获得 App 的新版本 此外 TestFlight 也适用于所有平台 当你发布新的构建版本时 TestFlight 会附上发布说明 让测试员了解应重点关注的方面 然后他们就能向你提供 反馈和分析数据 这些功能全都内置于 Xcode 之中 将 App 发布到 TestFlight 或 App Store 时 首先需要归档 归档是经过编译的 App 的快照 其中包含了一个发布版本 这个发布版本经过优化 可以节省空间 所以其中不再包含对问题进行调查 所需的调试信息 但这个归档中也会包含调试符号 所以如果你存储了这些符号 以后就可以对这个版本进行调试 你还可以重新打包 适用于所选目标设备的内容 来分发你的 App
当你准备好发布构建版本时 前往 Product 菜单 然后选择 Archive Xcode 会再次构建你的 App 然后为你打包 并在 Organizer 中显示结果 一旦 App 达到运行良好的状态 你可能不仅需要提交更改 还需要创建归档 以便 让这个运行良好的 App 进入可以轻松在设备上安装 或是进行分发的状态 为此 需要选择归档 然后点按 Distribute App 按钮 你会看到几个用于分发 App 的预设 “App Store Connect” 选项可以将 App 上传到 TestFlight 或 App Store Connect “TestFlight Internal Only” 会跳过 App 审核并加入保护措施 因此可以避免不小心 将 App 提交至 App Store 这个选项仅适用于你的 内容提供方/组织中的 Beta 版测试员 而不适用于外部测试员 “Release Testing”、 “Enterprise”和“Debugging” 选项都会生成经过优化的构建版本 以供用户使用在你的门户网站 注册过的设备进行安装 Xcode Cloud 也与 TestFlight 进行了整合 你可以设置一个工作流程 在构建成功且测试通过之后 直接提交至 TestFlight 以便测试员获取最新构建版本 你甚至可以配置脚本 通过从 git 提交信息中拉取内容 来自动生成测试员发布说明 如需进一步了解 如何使用 TestFlight 可以观看Tech Talks 视频“TestFlight 入门” 如需了解如何自动生成 测试员发布说明 可以阅读文章 “在 App 的 Beta 版本中 加入面向测试员的说明” 如需了解如何充分利用 Xcode Cloud 的强大功能 可以观看去年的视频“Xcode 和 Xcode Cloud 中的简化分发” 现在 我们的构建版本 已提交至 TestFlight 测试员运行的是最新 Beta 版 而用户使用的是最新发布版本 接下来 我们深入了解一下 Xcode Organizer 这个 Organizer 的快捷键是 Command-Option-Shift-O 这是一款 Xcode 内置工具 既能让你访问大量分析数据 又能妥善保护用户隐私 只有那些同意 与像你这样的第三方开发者 共享反馈信息和诊断数据的用户 才会纳入这里的分析报告范围 但一般情况下 你可以将这些用户视为 能够代表用户群体的样本 发布到 TestFlight 或 App Store 之后 你可以在这个窗口中找到接下来 要编辑、调试、测试 和提交的代码 像我们这样的软件工程师 进行这种迭代 是为了确保 App 能够 为我们的用户带来最佳体验 这也是我们作为项目经理的本职工作 因为总会有需要追踪的功能、 需要修复的错误以及 需要解决的回归问题 接下来 我们先查看一下 Feedback 标签 看看我们的下一项出色功能 应该是什么样的 因为研究功能是最有趣的事情 TestFlight Beta 版测试员 可以在这里向你提供反馈 而我们也希望聆听用户的反馈 我想我们应该立即着手开发 其中的一些功能 所以我要将这一项 添加到我们的下一次冲刺中 但首先 我要以项目经理的身份 直言不讳地指出 有一些质量问题需要我们解决 在 Launches Organizer 中 我可以看到发布的 App 版本 启动用时很长 在 App 启动时 从云端同步刷新数据的速度比较慢 也许这是因为我们仓促地 将这项功能发布到生产环境 我们本来可以采用更高效的方式 来编写这项功能 如果时间再充裕一点就好了 此外 发布上一个 Beta 版之后 终止运行的情况 出现了激增 我想我们需要将这些功能 推迟到未来的冲刺中 以便首先解决这些问题 查看这些数据时 要记住 用户使用的环境可能会 不同于你使用的环境 你的设备上可能装有 最新版本的操作系统 但用户可能并非如此 或者 现实世界中可能会 存在网络问题 而你在办公桌前 进行测试时不会遇到这种问题 正因为如此 我们需要 推出 Beta 版程序 来收集更多数据 以便了解 App 的状况 无论你是开发者、项目经理 还是产品营销合作伙伴 你都可以免费获取所有这些 内置于 Xcode 的信息 帮助推动产品开发沿着正确方向进行 今天 我们详细介绍了许多内容 包括如何进行导航、 提高编辑速度的窍门 以及如何组织你的代码 我介绍了使用断点 对棘手情况进行调试的技巧 以及如何将控制台 当作自己的“水晶球”来使用 我们了解了如何通过测试 在发布之前发现错误 进行这些测试时 Test Report 可以显示测试内容历史记录 然后我们谈到了如何用 TestFlight 将 App 分发给 Beta 版测试员 以及如何在 Organizer 中查看反馈
我和 Cheech 希望大家对于 如何优化 编辑、调试、测试和提交 工作流程都有了新的认识 感谢大家观看本次讲座 现在 让我们所有人 一起动手修复错误吧!
-
-
10:26 - Warning and error annotations
#warning("This is a warning annotation") #error("This is an error annotation")
-
10:58 - Mark comments
// MARK: This is a section title
-
14:09 - Placeholder
<#placeholder#>
-
17:30 - showStarView()
showStarView()
-
17:51 - Breakpoint #1
let task = URLSession.shared.dataTask(with: cloudURL, completionHandler: handleUpdatesFromCloud)
-
17:53 - Breakpoint #2
videos = loadVideosFromCloud()
-
18:17 - Swift error breakpoint
let url = try! getVideoResourceFilePath()
-
18:34 - Swift error throw
throw URLLoadError.fileNotFound
-
18:59 - Conditional breakpoint
cloudURL.scheme == "https"
-
19:18 - Print statement in conditional breakpoint
p "Username is \(cloudURL.user())"
-
19:44 - guard clause
guard cloudURLs.allSatisfy({ $0. scheme == "https" }), session.configuration.networkServiceType == .video else { return }
-
19:56 - p session
p session
-
19:58 - p first part of guard clause
cloudURLs.allSatisfy({ $0. scheme == "https" })
-
20:02 - p second part of guard clause
p session.configuration.networkServiceType == .video
-
20:11 - Random star rating
var starRating: Int { let randomStarRating = Int.random(n: 1..<5) return randomStarRating }
-
21:16 - Converting starRatingPercentage to Int
var starRating: Int { return Int((starRatingPercentage * 5).rounded()) }
-
21:46 - print statements for debugging
var releaseDate: Date { print("🎬 Entering func \(#function) in \(#fileID)...") let currentDate = Date() let gregorianCal = Calendar(identifier: .gregorian) var components = DateComponents() components.year = releaseYear print("\(#fileID)@\(#line) \(#function): 📅 releaseYear is \(releaseYear)") if releaseYear == gregorianCal.component(.year, from: currentDate) { components.month = Int(releaseMonth) isNewRelease = true print("\(#fileID)@\(#line) \(#function): 🆕 this is a new release!") } if releaseYear < 2000 { isClassicMovie = true print("\(#fileID)@\(#line) \(#function): 🎻 this one is a classic!") } let calendar = Calendar(identifier: .gregorian) return calendar.date(from: components)! }
-
22:09 - os_log statements for debugging
var releaseDate: Date { os_log(.debug, "🎬 Entering func \(#function) in \(#file)...") let currentDate = Date() let gregorianCal = Calendar(identifier: .gregorian) var components = DateComponents() components.year = releaseYear os_log(.info, "📅 releaseYear is \(releaseYear)") if releaseYear == gregorianCal.component(.year, from: currentDate) { components.month = Int(releaseMonth) isNewRelease = true os_log(.info, "🆕 this is a new release!") } if releaseYear < 2000 { isClassicMovie = true os_log(.info, "🎻 this one is a classic!") } let calendar = Calendar(identifier: .gregorian) return calendar.date(from: components)! }
-
23:19 - Sample unit tests
import Testing @testable import Destination_Video struct DestinationVideo_UnitTests { private var library = VideoLibrary() // Make sure starRating is returning a percentage @Test func testStarRating() async throws { for video in library.videos { #expect(video.info.starRating > 0) #expect(video.info.starRating <= 5) } } // Make sure the library loads data from the json file @Test func testLibraryLoaded() async throws { #expect(library.videos.count > 1) } }
-
24:15 - Sample UI tests
import XCTest final class Destination_VideoUITests: XCTestCase { private var app: XCUIApplication! @MainActor override func setUpWithError() throws { // UI tests must launch the application that they test. app = XCUIApplication() app.launch() // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false } @MainActor func testABeach() throws { // Tap the button to load the detail view for the "A Beach" video let aBeachButton = app.buttons["A Beach"].firstMatch aBeachButton.tap() // Make sure it has a Play Video button after going to that view let playButton = app.buttons["Play Video"] XCTAssert(playButton.exists) // Make sure the star rating for this video contains 4 stars to avoid issue we saw previously where it was only a single star because starRating was incorrectly a percentage instead of an Int let theRatingView = app.staticTexts["TheRating"] XCTAssert(theRatingView.label.contains("⭐️⭐️⭐️⭐️⭐️")) } @MainActor func testMainView() throws { // We should have at least 10 buttons for the various videos let buttons = app.buttons XCTAssert(buttons.count >= 10) // Check that the most popular videos have buttons for them for expectedVideo in ["By the Lake", "Camping in the Woods", "Ocean Breeze"] { XCTAssert(app.buttons[expectedVideo].exists) } } @MainActor func testLaunchPerformance() throws { if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { // This measures how long it takes to launch your application. measure(metrics: [XCTApplicationLaunchMetric()]) { XCUIApplication().launch() } } } }
-
24:19 - Swift Testing tags
@Test(.tags(.stars)) func testStarRating() async throws { for video in library.videos { #expect(video.info.starRating > 0) #expect(video.info.starRating <= 5) } } @Test(.tags(.library)) func testLibraryLoaded() async throws { #expect(library.videos.count > 1) } extension Tag { @Tag static var stars: Tag @Tag static var library: Tag }
-
26:35 - Running xcodebuild test from the command line
xcodebuild test -scheme DestinationVideo xcodebuild test -scheme DestinationVideo -testPlan TestAllTheThings xcodebuild test -scheme DestinationVideo -testPlan TestAllTheThings -only-testing "Destination VideoUITests/testABeach"
-
29:03 - Missing Code Coverage
func toggleUpNextState(for video: Video) { if !upNext.contains(video) { // Insert the video at the beginning of the list. upNext.insert(video, at: 0) } else { // Remove the entry with the matching identifier. upNext.removeAll(where: { $0.id == video.id }) } // Persist the Up Next state to disk. saveUpNext() }
-
29:19 - Code Coverage executed 5 times
init() { // Load all videos available in the library. videos = loadVideos() // The first time the app launches, set the last three videos as the default Up Next items. upNext = loadUpNextVideos(default: Array(videos.suffix(3))) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。