WWDC Quick Look 💓 By SwiftGGTeam
Profile and optimize power usage in your app

Profile and optimize power usage in your app

观看原视频

Highlight

Power Profiler 现在可以脱离 Xcode,在设备上直接采集功耗 trace,覆盖那些只在通勤、CarPlay、户外才会出现的耗电问题。


核心内容

很多 app 的耗电 bug 都有同一种症状:在工程师桌面上跑得好好的,到了用户手里就把电池吃光。Wiam 在演讲里举了自己的例子——Destination Video app 的同事抱怨这个 app 长期霸占电池排行榜第一,但她自己怎么试都复现不了。原因很直白,同事在通勤路上用,位置一直变;她坐在工位上,位置几乎不动,触发耗电的代码路径根本走不到。

WWDC25 的解法是把 Power Profiler 从 Xcode 里搬到了设备本身。开发者可以直接在 iPhone 上启动 Performance Trace,让 QA 或 beta 用户跑几个小时,然后把 trace 文件传回 Mac 用 Instruments 打开。整个流程不需要插线、不需要 Xcode 在场,覆盖了过去最难复现的几类场景:CarPlay 导航、AR 户外使用、长时间后台耗电。再加上原本桌面侧的 Power Profiler 模板(包含系统级功耗、CPU/GPU/Display/Network 四个子系统的 power impact,以及 Time Profiler),开发者第一次有了从工位到野外的完整功耗证据链。


详细内容

整段演讲围绕四个具体场景:可复现问题、不可复现问题、方案对比、主动监控。

场景一:可复现的 CPU 尖峰(02:24。Wiam 给 app 加了一个 Library pane,Xcode Organizer 的 energy report 立刻报警 CPU 用量飙升。她在 Instruments 选 blank 模板,加上 Power Profiler 和 CPU Profiler,连无线设备 Profile 一遍。结果在 CPU power impact 这条 lane 上,打开 Library pane 之前的平均值是 1,打开之后跳到 21(05:20)。Time Profiler 的 Heaviest Stack Trace 直接指向 VideoCardView——LibraryThumbnailView 用 VStack 一次性把上千个视频卡片全建出来了。

修复方式就是把 VStack 换成 LazyVStack(07:30):

// Before: 一次性创建所有 VideoCardView
struct LibraryThumbnailView: View {
    let videos: [Video]

    var body: some View {
        ScrollView {
            VStack {
                ForEach(videos) { video in
                    VideoCardView(video: video)
                }
            }
        }
    }
}

// After: 仅渲染可见区域内的视图
struct LibraryThumbnailView: View {
    let videos: [Video]

    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(videos) { video in
                    VideoCardView(video: video)
                }
            }
        }
    }
}

关键点:

  • VStack 在 body 求值时立即创建并保留所有子视图,列表越长,启动开销和内存占用越大。
  • LazyVStack 只对当前可见或即将滚入视口的 item 创建 view,滚出后销毁。
  • 数据源 videos 不变,View 树结构不变,仅改容器类型——这正是 SwiftUI 提供 Lazy 容器的原因。
  • 修改后再次 Profile,相同区间 CPU power impact 平均值从 21 降到 4.3(08:32)。

场景二:设备端采集(09:27。开启路径:Settings -> Developer -> Performance Trace -> 打开 Power Profiler 开关 -> 选定目标 app(仅限 Xcode、TestFlight、企业证书安装的 app)。然后从屏幕右上角下拉打开 Control Center,把 Performance Trace 图标加入并点击开始。可以连续录数小时,再次点击图标停止。生成的 trace 文件 AirDrop 给 Mac,用 Instruments 直接打开。设备端模式的 Time Profiler 采样率较低,目的是减少观察者效应。

Wiam 用同事发回的 trace 定位到 videoSuggestionsForLocation 占用大量 CPU 时间(13:32)。这个函数负责基于位置推荐附近视频,每次位置变化都会被调用。问题代码每次执行都重新读 RecommendationRules 这个体积很大的 JSON 文件并 decode 一次:

// 问题:每次位置更新都重新读文件 + 全量解析 JSON
func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    let url = Bundle.main.url(forResource: "RecommendationRules", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    let rules = try! JSONDecoder().decode([RecommendationRule].self, from: data)
    return filter(videos: allVideos, with: rules, near: location)
}

// 优化:rules 在运行期不变,懒加载 + 缓存
private static let recommendationRules: [RecommendationRule] = {
    let url = Bundle.main.url(forResource: "RecommendationRules", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    return try! JSONDecoder().decode([RecommendationRule].self, from: data)
}()

func videoSuggestionsForLocation(_ location: CLLocation) -> [Video] {
    return filter(videos: allVideos, with: Self.recommendationRules, near: location)
}

关键点:

  • 文件 I/O 和 JSON 解析在高频回调里是典型的功耗陷阱,越大的 JSON、越频繁的位置更新,影响越严重。
  • 把 rules 提到 static let + 立即执行的闭包里,等价于线程安全的懒加载(Swift 保证 let 静态属性的初始化只发生一次)。
  • 修复后请同事在相同的通勤场景再录一份 trace 做对比,确认问题修复且没有引入新的功耗回归(15:51)。

场景三:方案对比(16:19。两个候选实现 A、B,可能 A 在小数据下更省 CPU,B 在大数据下更省网络。直接看代码无法判断净效益。做法是分别 Profile,多次运行取平均,控制 thermal 状态、设备状态、系统压力、app 内功能开关一致,再做横向对比。

场景四:纵深防御(18:25。Apple 的官方建议是把功耗工具串成流水线:写代码时看 Xcode Energy Gauges 实时反馈 -> 怀疑问题时上 Instruments Power Profiler 深挖 -> CI 用 XCTests 自动检测 -> 上线后用 Xcode Organizer / MetricKit 监控真实用户 -> 用 App Store Connect API 拉远端聚合数据。


核心启发

  • 现在就跑一次 Power Profiler trace:连上设备,Profile 一遍主要使用路径,看 CPU/GPU power impact 有没有意外尖峰。这是成本最低的功耗审计,没找到问题也是好结果。
  • 把 VStack/HStack 默认替换成 LazyVStack/LazyHStack:除非数据量明确小于 20 项,列表/网格就用 Lazy 容器。这是 Wiam 案例里 21 -> 4.3 的关键修改,几乎零代码成本。
  • 设备端 Performance Trace 用于 dogfood / QA:让团队成员在真实通勤、户外、CarPlay 场景下采集 trace 回传。把这个流程写进 beta 测试文档,能挖出工位上永远复现不了的耗电 bug。
  • 位置/timer/通知等高频回调禁止做文件 I/O 和 JSON 解析:把不可变数据用 static let 闭包做懒加载缓存,是修复这类问题最直接的模式。
  • 每次性能修改后录一份对比 trace:确认修复同时确认没有引入新回归。Power Profiler 的对比方法论需要控制 thermal、设备状态等变量,多次运行取平均。

关联 Session

评论

GitHub Issues · utterances