Highlight
Instruments 13 在 Network 模板中加入 HTTP Traffic Instrument,让开发者按进程、URLSession、URLSessionTask 和 HTTP transaction 查看 App 的 HTTP 流量,用同一份 trace 定位性能问题、缓存错误、认证问题和隐私风险。
核心内容
一个 App 的网络问题,经常不会直接出现在代码里。
图片加载慢,代码里只看到一组 URLSessionTask。用户反复登录,代码里只看到服务器返回了 401。收藏列表没有更新,界面看起来像状态管理错误。第三方 SDK 偷发数据,源码甚至不在你的工程里。
以前排查这些问题,需要在客户端日志、服务器日志、代理抓包工具之间来回切换。每一种工具只能看到一部分事实。你知道请求发出去了,却不一定知道它属于哪个 URLSession。你看到 HTTP header,却不一定知道是哪一行代码调用了 resume()。
Apple 在 Instruments 13 的 Network 模板里加入了 HTTP Traffic Instrument。它直接接入 Apple Networking stack(Apple 网络栈),记录经过 URL Loading System(URL 加载系统)的 HTTP 流量。HTTP/3、VPN 流量、磁盘缓存命中、网络错误,都会在同一条时间线上出现。
这场演讲用一个狗狗图片 App 做演示。首页加载慢,是服务器只支持 HTTP/1.1,导致同一连接上的请求排队。登录状态丢失,是服务器发回了过期 cookie。收藏列表不刷新,是请求直接命中了本地缓存。第三方登录 SDK 在用户点击前就上传位置坐标,是隐私问题。
关键变化是,网络请求不再只是 URL 和状态码。Instruments 把它放回开发者熟悉的对象模型里:进程、URLSession、URLSessionTask、transaction(一次 HTTP 请求和响应)。你可以从时间线看到问题,也可以从 backtrace(回溯)追到源码。
详细内容
1. 用对象层级阅读 HTTP 流量
(02:04)HTTP Traffic Instrument 的顶层轨道显示 trace 中正在运行的 URLSessionTask 数量。下一层按进程拆分,再下一层是进程使用的 URLSession。最底层按请求域名拆分,并显示 task 和 transaction 的状态。
如果工程里有多个 session,默认名字很难和代码对应。Apple 建议给 session 和 task 加语义名称。Instruments 会用这些名称标注时间线。
import Foundation
final class FeedLoader {
private let session: URLSession
init() {
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration)
session.sessionDescription = "DogFeed Image Loader"
}
func loadThumbnail(from url: URL) {
let task = session.dataTask(with: url) { data, response, error in
if let error {
print("thumbnail failed: \(error)")
return
}
print("thumbnail bytes: \(data?.count ?? 0)")
}
task.taskDescription = "Load latest dog thumbnail"
task.resume()
}
}
关键点:
URLSessionConfiguration.default创建默认 URLSession 配置。URLSession(configuration:)创建一个独立 session,它会出现在 Instruments 的 session 层级里。session.sessionDescription给 session 加名字,方便把时间线和代码对象对应起来。session.dataTask(with:)创建一个URLSessionTask。task.taskDescription给 task 加名字,Instruments 会用它标注 task interval(任务区间)。task.resume()启动请求,演讲中说明 task interval 从这里开始,直到 completion block 调用前结束。
2. 用 transaction 状态定位 HTTP/1 队头阻塞
(13:18)演示 App 首页需要先请求图片列表,再并发请求每张缩略图。第一次 trace 显示,初始屏幕加载超过 7 秒。后启动的缩略图 task 变慢,紫色 Blocked 状态越来越长。
切到 HTTP Transactions by Connection 视图后,问题变清楚了。每个 transaction 都要等同一连接上的前一个 transaction 完成,才能发送自己的请求。这是 HTTP/1 的 Head of Line Blocking(队头阻塞)。服务器启用 HTTP/2 后,请求可以在一个连接上多路复用,总耗时降到 3 秒以内。
Instruments
→ Profile with Network template
→ HTTP Traffic
→ Select app process
→ Select server domain
→ Track display: HTTP Transactions by Connection
→ Look for: increasing Blocked state on the same connection
→ Check label: HTTP version
关键点:
Network template同时包含 Network Connections instrument 和 HTTP Traffic instrument。HTTP Traffic顶层轨道用来定位流量高峰。Select server domain可以只看某个后端域名的请求。HTTP Transactions by Connection会按连接分组 transaction,用来判断请求是否排在同一连接上。increasing Blocked state是本场演示中定位队头阻塞的直接信号。HTTP version会显示在 transaction label 上,用来确认请求走的是 HTTP/1.1、HTTP/2 或其他版本。
3. 用 transaction label 检查 cookie 和认证流程
(18:37)用户第一次收藏图片时,服务器返回 401。用户输入账号密码后,URL Loading System 重试 transaction,并收到 201。两个 transaction 属于同一个 task,因为这是同一次认证挑战和重试流程。
第二次收藏图片时,App 又要求登录。Instruments 显示请求再次收到 401,请求 label 上没有 Cookie 图标。回看上一次成功登录的 response,服务器确实发了 Set-Cookie。展开 transaction detail 后发现,cookie 的过期时间是 2020 年 3 月,已经过期,所以 URLSession 后续不会发送它。
HTTP/1.1 201 Created
Set-Cookie: session=abc123; Expires=Wed, 18 Mar 2020 12:00:00 GMT; Path=/; Secure; HttpOnly
Content-Type: application/json
关键点:
201 Created对应演示中第一次登录后重试成功的状态码。Set-Cookie表示服务器尝试设置登录 cookie。Expires=Wed, 18 Mar 2020 12:00:00 GMT是问题根源,演讲中指出这个日期已经在过去。Path=/限制 cookie 的路径范围。Secure表示 cookie 只通过安全连接发送。HttpOnly表示客户端脚本不能读取该 cookie。- 过期 cookie 不会被
URLSession发送,所以下一次收藏请求仍然没有 Cookie header。
4. 用缓存策略修复过期列表
(24:40)收藏列表没有显示新收藏的图片。Instruments 里能找到加载收藏列表的 task,但它只用了几毫秒。切到 HTTP Transactions by Connection 后发现,这个 transaction 没有跑在网络连接上,它跑在 Local Cache 上。
这说明请求没有发给服务器。演讲中的修复方式是在 URLRequest 上设置 reloadRevalidatingCacheData。这样客户端会向服务器确认缓存是否仍然有效。服务器可以返回 304,让客户端继续使用本地缓存;如果数据变化,服务器返回新数据。
import Foundation
func loadFavorites(from endpoint: URL, session: URLSession = .shared) {
var request = URLRequest(url: endpoint)
request.cachePolicy = .reloadRevalidatingCacheData
let task = session.dataTask(with: request) { data, response, error in
if let error {
print("favorites failed: \(error)")
return
}
if let httpResponse = response as? HTTPURLResponse {
print("status: \(httpResponse.statusCode)")
}
print("favorites bytes: \(data?.count ?? 0)")
}
task.taskDescription = "Reload favorites with cache revalidation"
task.resume()
}
关键点:
URLRequest(url:)创建要加载收藏列表的请求。request.cachePolicy控制这一次请求如何使用本地缓存。.reloadRevalidatingCacheData是演讲中使用的缓存策略,会向服务器确认缓存有效性。session.dataTask(with: request)使用带缓存策略的 request 创建 task。HTTPURLResponse.statusCode可以打印服务器返回的状态码;演讲提到服务器可用 304 表示缓存仍有效。task.taskDescription让 Instruments 中的 task 更容易识别。task.resume()发起请求。
5. 用 backtrace 和 HAR 导出审计第三方 SDK
(29:19)演讲最后检查一个第三方登录 SDK。App 刚进入登录页,还没有点击 SDK 的登录按钮,Instruments 已经显示 Pets Sign On Network session 在发送请求。请求是 POST,backtrace 显示它来自 SDK,并且牵涉到 CoreLocation。
展开 transaction detail 后,请求 body 里包含位置坐标。演讲明确指出,这种行为会侵犯用户隐私。开发者可以保存 trace,然后用 xctrace 导出 HTTP Archive(HAR)文件,发给 SDK 团队复现问题。
xcrun xctrace export \
--input ~/Desktop/PrivacyViolation.trace \
--har \
--output ~/Desktop/PrivacyViolation.har
关键点:
xcrun调用 Xcode 附带的命令行工具。xctrace export导出 Instruments trace 中的数据。--input ~/Desktop/PrivacyViolation.trace指定演讲中保存的 trace 文件。--har选择 HTTP Archive 格式,用于交换 HTTP 流量信息。- HAR 是 JSON-based format(基于 JSON 的格式),可以用支持 HAR 的工具、文本编辑器或脚本检查。
- 演讲说明 HAR 不包含 Instruments 专有信息,例如
URLSession和 backtrace,但足够让对方调查 HTTP 内容。
核心启发
1. 给网络层加可观测名称
- 做什么:为每个业务场景的
URLSession和关键URLSessionTask设置描述,例如图片加载、登录、收藏同步。 - 为什么值得做:HTTP Traffic Instrument 会把
sessionDescription和taskDescription显示在时间线上,排查问题时不用靠 URL 猜业务含义。 - 怎么开始:在网络封装初始化
URLSession后设置session.sessionDescription,在创建 task 后设置task.taskDescription,再用 Network 模板录制一次核心流程。
2. 建一个首屏网络性能检查流程
- 做什么:每次改首页接口或图片加载策略后,用 Instruments 检查首屏请求是否出现长时间 Blocked 状态。
- 为什么值得做:演讲中的首页从超过 7 秒降到 3 秒以内,核心线索就是
HTTP Transactions by Connection里的队头阻塞。 - 怎么开始:录制 App 启动到首屏完成的 trace,按域名展开 HTTP Traffic,切到
HTTP Transactions by Connection,检查 HTTP version 和 Blocked 状态。
3. 为登录状态写一份 cookie 审计清单
- 做什么:把登录、重试、再次请求受保护资源这条链路录成 trace,确认服务器是否设置了有效 cookie,后续请求是否带上 Cookie header。
- 为什么值得做:演讲中的 bug 不在客户端保存逻辑,而在服务器返回了过期
Set-Cookie。 - 怎么开始:在 transaction detail 中查看 response headers 的
Set-Cookie,再查看下一次 request label 或 request headers 是否包含 Cookie。
4. 给会变化的列表加缓存复核
- 做什么:对收藏列表、通知列表、协作数据这类会变化的数据,按需使用缓存复核策略。
- 为什么值得做:演讲中的收藏列表命中
Local Cache,导致用户看不到新收藏。reloadRevalidatingCacheData可以让客户端向服务器确认缓存是否过期。 - 怎么开始:找到对应
URLRequest,设置request.cachePolicy = .reloadRevalidatingCacheData,再用 Instruments 确认 transaction 是否走到连接或收到 304。
5. 把第三方 SDK 接入前的隐私检查自动化
- 做什么:集成 SDK 后,录制 App 冷启动、进入登录页、点击 SDK 按钮前后的 HTTP 流量,检查是否有未预期的请求体、位置数据或认证信息。
- 为什么值得做:HTTP Traffic Instrument 能显示 SDK 自己创建的 session、POST 请求、headers、body 和 backtrace。
- 怎么开始:用 Network 模板录制 trace,选择 SDK session,查看 transactions detail;发现问题后用
xcrun xctrace export --har导出 HAR 附到 bug report。
关联 Session
- Accelerate networking with HTTP/3 and QUIC — 解释 HTTP/2 多路复用和 HTTP/3/QUIC,能补足本场关于队头阻塞的协议背景。
- Use async/await with URLSession — 展示 URLSession 在 Swift Concurrency 下的写法,适合和本场的 URLSession 诊断方法一起看。
- Symbolication: Beyond the basics — 讲解符号化如何让 Instruments 的 backtrace 对应回源码。
- Optimize for 5G networks — 讨论移动网络下的数据传输优化,和本场的 HTTP 流量审计目标相邻。
评论
GitHub Issues · utterances