Highlight
Xcode 团队从”编辑-调试-测试-提交”这个日常开发循环出发,系统梳理了导航器过滤、Find Navigator 高级搜索、断点条件与自动继续、Swift Testing 标签过滤、Code Coverage 可视化、以及 TestFlight 分发等完整工作流中的效率提升技巧。
核心内容
项目越来越大,团队越来越多,“找到要改的代码”本身就成了问题的一半。你可能在一两百个文件里搜索一个符号,可能在一堆搜索结果里手动翻找,也可能在调试时反复重启应用只为了重现一次崩溃。这个 Session 的目标就是帮你缩短这些环节的耗时。
上半场由设计师 Cheech 讲编辑和导航。核心思路是:Xcode 的每个导航器底部都有过滤栏,Find Navigator 支持搜索范围保存和符号索引模式,Tab 分为永久和隐式两种,Jump Bar 和 Open Quickly(Command-Shift-O)是最快的文件跳转方式。这些功能零散分布在界面各处,组合起来能大幅减少”找代码”的时间。
下半场由项目经理 Myke 讲调试、测试和分发。调试部分的重点是断点的高级用法:条件断点、自动继续的日志断点、Swift Error Breakpoint、以及拖拽绿色程序计数器回退执行。测试部分介绍了 Swift Testing 的标签过滤、Test Plans 的跨 scheme 复用、Code Coverage 的行级可视化。分发部分则覆盖了 Archive 流程、TestFlight 内部/外部测试、以及 Xcode Organizer 里的反馈和崩溃数据。
详细内容
导航与搜索
Project Navigator 底部过滤栏支持按文件名搜索、按 target 名称过滤、按 git status 过滤(只显示已修改文件)。
Find Navigator(Command-Shift-F)的高级用法:
- 底部过滤栏可在搜索结果中做二次筛选,按文件名或匹配行中的额外关键词过滤
- Command-点击搜索结果中文件名旁的展开箭头,折叠所有同级文件(适用于所有 outline 视图)
- 选中不需要的结果按 Delete 键,从当前查询中隐藏(不会删除代码)
- 搜索范围(Scope)可限定到特定目录,常用范围可保存
- 点击搜索框上方的”Text”切换到符号索引模式,“Descendant Types”可查看类层级(05:10)
选中文字后按 Command-E,文字进入搜索框但不覆盖剪贴板,这在 macOS 全局有效(05:27)。
文件间移动与编辑
Xcode 的 Tab 有两种类型:永久 Tab(你主动编辑的文件)和隐式 Tab(只浏览过、斜体标题标识、离开即消失)。双击隐式 Tab 或右键选”Keep Open”可转为永久 Tab。Option-点击关闭按钮可关闭除当前外的所有 Tab(06:32)。
Open Quickly(Command-Shift-O)是 Xcode 里最快的跳转方式:输入文件名或符号的部分关键词即可跳转,查询中加斜杠匹配路径,加冒号和行号跳到指定行。按住 Option 再按 Return 在新分栏中打开(11:22)。
Quick Actions(Command-Shift-A)用自然语言搜索 Xcode 命令,适合记不住快捷键的场景。
Xcode 16 的 Predictive Code Completion 会根据上下文预测整行代码,按 Tab 接受可见部分,按 Option-Tab 接受完整预测。注释和变量名越具描述性,预测越准确(14:24)。
断点高级用法
对于高频命中的代码,可以使用双断点配合:在按钮处理器设断点,命中后手动启用忙碌函数中的断点再继续,这样就只在特定场景下停下(17:39)。
Swift Error Breakpoint 在 Breakpoint Navigator 中添加,应用会在任何 Swift 错误抛出处停下,而不是只停在 catch(18:16):
let url = try! getVideoResourceFilePath()
关键点:
try!强制解包,如果失败会触发运行时错误- Swift Error Breakpoint 能在
throw发生时立即暂停,而不是等到崩溃
条件断点:双击断点编辑,添加条件表达式,只在条件为真时暂停。也可以添加打印表达式并设置”自动继续”,这样就不用重新编译就能获得临时日志(18:59):
cloudURL.scheme == "https"
关键点:
- 这是一个布尔表达式,只有当 URL 的 scheme 为 “https” 时断点才会暂停
- 适合在高流量代码中只关注特定条件的场景
在断点中添加日志表达式并设置自动继续:
p "Username is \(cloudURL.user())"
关键点:
p是 LLDB 的 print 命令,在控制台输出表达式的值- 搭配”Automatically continue”选项,无需暂停程序即可记录信息
调试器回溯与回退
在调试器中,可以用 p 命令回溯分析 guard 语句为什么提前返回(19:44):
guard cloudURLs.allSatisfy({ $0. scheme == "https" }),
session.configuration.networkServiceType == .video else {
return
}
分别用 p cloudURLs.allSatisfy({ $0.scheme == "https" }) 和 p session.configuration.networkServiceType == .video 检查每个条件的值,找出哪个条件为假。
绿色程序计数器可以向后拖拽,重新执行之前的表达式。副作用不会回退,但比完全重启调试会话更快(20:11)。
Command-Control-R “Run Without Building” 跳过编译直接运行上次的构建产物,适合需要反复调试但代码未改的场景(21:02)。
Xcode 16 支持在编辑器中查看完整回溯(Full Backtraces),从 Debug Bar 激活,把来自不同文件的调用栈集中在同一编辑器视图中(21:20)。
从 print 迁移到 os_log
Session 对比了 print 和 os_log 两种调试日志方式。print 语句的输出难以过滤和定位来源:
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)!
}
用 os_log 替代后,每条日志带有级别(debug/info/error),可以按文本、级别、来源库过滤,点击箭头直接跳转到源码行(22:09):
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)!
}
关键点:
os_log(.debug, ...)和os_log(.info, ...)为每条日志设定级别- 控制台可按级别、文本、来源库过滤,不需要手动加
#fileID等宏 - 日志条目带跳转箭头,点击直接定位到源码行
Swift Testing 与标签过滤
Swift Testing 用 @Test 标记测试函数,#expect 替代 XCTAssert。标签(Tag)可以对测试分组过滤(24:19):
@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
}
关键点:
.tags(.stars)给测试打标签,在 Test Navigator 中可按标签过滤#expect是 Swift Testing 的断言宏,比 XCTAssert 语法更简洁- Tag 定义放在
extension Tag中作为静态属性
命令行运行测试时可以指定 scheme、test plan、甚至单个测试(26:35):
xcodebuild test -scheme DestinationVideo
xcodebuild test -scheme DestinationVideo -testPlan TestAllTheThings
xcodebuild test -scheme DestinationVideo -testPlan TestAllTheThings -only-testing "Destination VideoUITests/testABeach"
关键点:
- 第一个命令运行 scheme 下所有测试
-testPlan指定测试计划-only-testing只运行特定测试用例,适合配合git bisect定位回归
Code Coverage
在 Editor 菜单启用 Code Coverage 后运行测试,编辑器右侧显示每行代码被执行的次数。0 表示未覆盖,可能意味着测试有缺口或代码从未被调用(29:03)。
Report Navigator(Command-9)提供文件级和函数级的覆盖率概览,帮助定位改进目标。
Test Report 中点击失败的测试,可以看到执行事件序列与屏幕录制并排显示,时间线底部标注失败发生的时间点(29:57)。
分发与 Organizer
Archive 是编译后应用的快照,包含 release 构建和 debug symbols。分发选项包括:App Store Connect(上传到 TestFlight 或 App Store Connect)、TestFlight Internal Only(跳过 App Review、仅限组织内部测试者)、Release Testing / Enterprise / Debugging(生成给注册设备安装的构建)(31:54)。
Xcode Organizer(Command-Option-Shift-O)汇总 TestFlight 反馈、启动耗时、终止率等数据,数据来自同意共享的用户,隐私安全。反馈标签页显示测试者的文字反馈,启动和终止标签页帮助定位性能问题和崩溃(33:36)。
核心启发
-
做什么:在 Find Navigator 中保存常用搜索范围。为什么值得做:大型项目中反复搜索同一组目录是常见操作,保存 scope 后一键切换,省去每次手动选择。怎么开始:搜索时点击搜索框下方的范围菜单,选 “Custom Scopes…”,选定目录后点保存。
-
做什么:用条件断点 + 自动继续替代临时 print 语句。为什么值得做:不需要修改代码、不需要重新编译,断点日志直接输出到控制台,调试完删除断点即可。怎么开始:双击断点打开编辑器,添加
p表达式,勾选 “Automatically continue after evaluating actions”。 -
做什么:把项目中的 print 调试日志迁移到 os_log。为什么值得做:os_log 支持按级别过滤、按来源库过滤、点击跳转到源码行,团队协作时控制台输出更可控。怎么开始:将
print("...")替换为os_log(.debug, "..."),删掉手动的#fileID和#function宏。 -
做什么:给 Swift Testing 测试加上 Tag 标签。为什么值得做:测试数量增长后,按标签过滤比按名称搜索更高效,Test Navigator 中一键筛选特定模块的测试。怎么开始:在测试函数的
@Test属性中添加.tags(.yourTag),在extension Tag中定义静态标签。 -
做什么:用 Command-Control-R(Run Without Building)加速反复调试。为什么值得做:调试崩溃时经常需要重启应用,如果代码没改就无需重新编译,省下等待构建的时间。怎么开始:下次调试循环中直接按 Command-Control-R 代替 Command-R。
关联 Session
- Run, Break, Inspect: Explore effective debugging in Xcode — 深入调试技巧,包括断点、LLDB 和视图调试
- Meet Swift Testing — Swift Testing 框架的介绍,替代 XCTest 的新测试范式
- Debug with structured logging — os_log 的结构化日志与控制台过滤
- What’s new in Xcode 16 — Xcode 16 新功能总览
评论
GitHub Issues · utterances