axiom-energy-ref
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEnergy Optimization Reference
iOS能源优化参考文档
Complete API reference for iOS energy optimization, with code examples from WWDC sessions and Apple documentation.
Related skills: (decision trees, patterns), (troubleshooting)
axiom-energyaxiom-energy-diag这是一份完整的iOS能源优化API参考文档,包含WWDC会话和Apple官方文档中的代码示例。
相关技能:(决策树、模式识别)、(故障排查)
axiom-energyaxiom-energy-diagPart 1: Power Profiler Workflow
第一部分:Power Profiler工作流
Recording a Trace with Instruments
使用Instruments录制性能追踪
Tethered Recording (Connected to Mac)
有线录制(连接Mac)
1. Connect iPhone wirelessly to Xcode
- Xcode → Window → Devices and Simulators
- Enable "Connect via network" for your device
2. Profile your app
- Xcode → Product → Profile (Cmd+I)
- Select Blank template
- Click "+" → Add "Power Profiler"
- Optionally add "CPU Profiler" for correlation
3. Record
- Select your app from target dropdown
- Click Record (red button)
- Use app normally for 2-3 minutes
- Click Stop
4. Analyze
- Expand Power Profiler track
- Examine per-app lanes: CPU, GPU, Display, NetworkImportant: Use wireless debugging. When device is charging via cable, system power usage shows 0.
1. 将iPhone无线连接到Xcode
- Xcode → 窗口 → 设备和模拟器
- 为你的设备启用“通过网络连接”
2. 分析你的应用
- Xcode → 产品 → 分析(Cmd+I)
- 选择空白模板
- 点击“+” → 添加“Power Profiler”
- 可选择性添加“CPU Profiler”用于关联分析
3. 录制
- 从目标下拉菜单中选择你的应用
- 点击录制(红色按钮)
- 正常使用应用2-3分钟
- 点击停止
4. 分析
- 展开Power Profiler轨道
- 检查各应用的细分项:CPU、GPU、显示屏、网络重要提示:使用无线调试。当设备通过线缆充电时,系统功耗会显示为0。
On-Device Recording (Without Mac)
设备端录制(无需Mac)
From WWDC25-226: Capture traces in real-world conditions.
1. Enable Developer Mode
Settings → Privacy & Security → Developer Mode → Enable
2. Enable Performance Trace
Settings → Developer → Performance Trace → Enable
Set tracing mode to "Power Profiler"
Toggle ON your app in the app list
3. Add Control Center shortcut
Control Center → Tap "+" → Add a Control → Performance Trace
4. Record
Swipe down → Tap Performance Trace icon → Start
Use app (can record up to 10 hours)
Tap Performance Trace icon → Stop
5. Share trace
Settings → Developer → Performance Trace
Tap Share button next to trace file
AirDrop to Mac or email to developer来自WWDC25-226:在真实场景下捕获性能追踪数据。
1. 启用开发者模式
设置 → 隐私与安全性 → 开发者模式 → 启用
2. 启用性能追踪
设置 → 开发者 → 性能追踪 → 启用
将追踪模式设置为“Power Profiler”
在应用列表中开启你的应用
3. 添加控制中心快捷方式
控制中心 → 点击“+” → 添加“性能追踪”控件
4. 录制
向下滑动 → 点击性能追踪图标 → 开始
使用应用(最长可录制10小时)
点击性能追踪图标 → 停止
5. 分享追踪数据
设置 → 开发者 → 性能追踪
点击追踪文件旁的分享按钮
通过AirDrop发送到Mac或邮件发送给开发者Interpreting Power Profiler Metrics
解读Power Profiler指标
| Lane | Meaning | What High Values Indicate |
|---|---|---|
| System Power | Overall battery drain rate | General energy consumption |
| CPU Power Impact | Processor activity score | Computation, timers, parsing |
| GPU Power Impact | Graphics rendering score | Animations, blur, Metal |
| Display Power Impact | Screen power usage | Brightness, content type |
| Network Power Impact | Radio activity score | Requests, downloads, polling |
Key insight: Values are scores for comparison, not absolute measurements. Compare before/after traces on the same device.
| 轨道 | 含义 | 高数值表明 |
|---|---|---|
| 系统功耗 | 整体电池消耗速率 | 总体能源消耗情况 |
| CPU功耗影响 | 处理器活动评分 | 计算任务、计时器、解析操作 |
| GPU功耗影响 | 图形渲染评分 | 动画、模糊效果、Metal框架使用 |
| 显示屏功耗影响 | 屏幕功耗 | 亮度、显示内容类型 |
| 网络功耗影响 | 无线模块活动评分 | 请求、下载、轮询操作 |
关键要点:这些数值是用于对比的评分,而非绝对测量值。请在同一设备上对比优化前后的追踪数据。
Comparing Before/After (Example from WWDC25-226)
优化前后对比(来自WWDC25-226示例)
swift
// Before optimization: CPU Power Impact = 21
VStack {
ForEach(videos) { video in
VideoCardView(video: video)
}
}
// After optimization: CPU Power Impact = 4.3
LazyVStack {
ForEach(videos) { video in
VideoCardView(video: video)
}
}swift
// 优化前:CPU功耗影响 = 21
VStack {
ForEach(videos) { video in
VideoCardView(video: video)
}
}
// 优化后:CPU功耗影响 = 4.3
LazyVStack {
ForEach(videos) { video in
VideoCardView(video: video)
}
}Part 2: Timer Efficiency APIs
第二部分:计时器效率API
NSTimer with Tolerance
带容差的NSTimer
swift
// Basic timer with tolerance
let timer = Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true
) { [weak self] _ in
self?.updateUI()
}
timer.tolerance = 0.1 // 10% minimum recommended
// Add to run loop (if not using scheduledTimer)
RunLoop.current.add(timer, forMode: .common)
// Always invalidate when done
deinit {
timer.invalidate()
}swift
// 基础带容差计时器
let timer = Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true
) { [weak self] _ in
self?.updateUI()
}
timer.tolerance = 0.1 // 建议最小容差为10%
// 添加到运行循环(如果未使用scheduledTimer)
RunLoop.current.add(timer, forMode: .common)
// 完成后务必失效
deinit {
timer.invalidate()
}Combine Timer Publisher
Combine Timer Publisher
swift
import Combine
class ViewModel: ObservableObject {
private var cancellables = Set<AnyCancellable>()
func startPolling() {
Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .default)
.autoconnect()
.sink { [weak self] _ in
self?.refresh()
}
.store(in: &cancellables)
}
func stopPolling() {
cancellables.removeAll()
}
}swift
import Combine
class ViewModel: ObservableObject {
private var cancellables = Set<AnyCancellable>()
func startPolling() {
Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .default)
.autoconnect()
.sink { [weak self] _ in
self?.refresh()
}
.store(in: &cancellables)
}
func stopPolling() {
cancellables.removeAll()
}
}Dispatch Timer Source (Low-Level)
底层Dispatch Timer Source
From Energy Efficiency Guide:
swift
let queue = DispatchQueue(label: "com.app.timer")
let timer = DispatchSource.makeTimerSource(queue: queue)
// Set interval with leeway (tolerance)
timer.schedule(
deadline: .now(),
repeating: .seconds(1),
leeway: .milliseconds(100) // 10% tolerance
)
timer.setEventHandler { [weak self] in
self?.performWork()
}
timer.resume()
// Cancel when done
timer.cancel()来自《能源效率指南》:
swift
let queue = DispatchQueue(label: "com.app.timer")
let timer = DispatchSource.makeTimerSource(queue: queue)
// 设置带误差范围的时间间隔(容差)
timer.schedule(
deadline: .now(),
repeating: .seconds(1),
leeway: .milliseconds(100) // 10%容差
)
timer.setEventHandler { [weak self] in
self?.performWork()
}
timer.resume()
// 完成后取消
timer.cancel()Event-Driven Alternative to Timers
计时器的事件驱动替代方案
From Energy Efficiency Guide: Prefer dispatch sources over polling.
swift
// Monitor file changes instead of polling
let fileDescriptor = open(filePath.path, O_EVTONLY)
let source = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: fileDescriptor,
eventMask: [.write, .delete],
queue: .main
)
source.setEventHandler { [weak self] in
self?.handleFileChange()
}
source.setCancelHandler {
close(fileDescriptor)
}
source.resume()来自《能源效率指南》:优先使用调度源而非轮询。
swift
// 监控文件变化而非轮询
let fileDescriptor = open(filePath.path, O_EVTONLY)
let source = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: fileDescriptor,
eventMask: [.write, .delete],
queue: .main
)
source.setEventHandler { [weak self] in
self?.handleFileChange()
}
source.setCancelHandler {
close(fileDescriptor)
}
source.resume()Part 3: Network Efficiency APIs
第三部分:网络效率API
URLSession Configuration
URLSession配置
swift
// Standard configuration with energy-conscious settings
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true // Don't fail immediately
config.allowsExpensiveNetworkAccess = false // Prefer WiFi
config.allowsConstrainedNetworkAccess = false // Respect Low Data Mode
let session = URLSession(configuration: config)swift
// 具备节能设置的标准配置
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true // 不立即失败
config.allowsExpensiveNetworkAccess = false // 优先使用WiFi
config.allowsConstrainedNetworkAccess = false // 遵循低数据模式
let session = URLSession(configuration: config)Discretionary Background Downloads
可自主调度的后台下载
From WWDC22-10083:
swift
// Background session for non-urgent downloads
let config = URLSessionConfiguration.background(
withIdentifier: "com.app.downloads"
)
config.isDiscretionary = true // System chooses optimal time
config.sessionSendsLaunchEvents = true
// Set timeouts
config.timeoutIntervalForResource = 24 * 60 * 60 // 24 hours
config.timeoutIntervalForRequest = 60
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
// Create download task with scheduling hints
let task = session.downloadTask(with: url)
task.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60 * 60) // 2 hours from now
task.countOfBytesClientExpectsToSend = 200 // Small request
task.countOfBytesClientExpectsToReceive = 500_000 // 500KB response
task.resume()来自WWDC22-10083:
swift
// 用于非紧急下载的后台会话
let config = URLSessionConfiguration.background(
withIdentifier: "com.app.downloads"
)
config.isDiscretionary = true // 由系统选择最佳时间
config.sessionSendsLaunchEvents = true
// 设置超时时间
config.timeoutIntervalForResource = 24 * 60 * 60 // 24小时
config.timeoutIntervalForRequest = 60
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
// 创建带调度提示的下载任务
let task = session.downloadTask(with: url)
task.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60 * 60) // 2小时后开始
task.countOfBytesClientExpectsToSend = 200 // 小请求
task.countOfBytesClientExpectsToReceive = 500_000 // 500KB响应
task.resume()Background Session Delegate
后台会话代理
swift
class DownloadDelegate: NSObject, URLSessionDownloadDelegate {
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
// Move file from temp location
let destination = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0].appendingPathComponent("downloaded.data")
try? FileManager.default.moveItem(at: location, to: destination)
}
func urlSessionDidFinishEvents(
forBackgroundURLSession session: URLSession
) {
// Notify app delegate to call completion handler
DispatchQueue.main.async {
if let handler = AppDelegate.shared.backgroundCompletionHandler {
handler()
AppDelegate.shared.backgroundCompletionHandler = nil
}
}
}
}swift
class DownloadDelegate: NSObject, URLSessionDownloadDelegate {
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
// 将文件从临时位置移动
let destination = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0].appendingPathComponent("downloaded.data")
try? FileManager.default.moveItem(at: location, to: destination)
}
func urlSessionDidFinishEvents(
forBackgroundURLSession session: URLSession
) {
// 通知应用代理调用完成处理程序
DispatchQueue.main.async {
if let handler = AppDelegate.shared.backgroundCompletionHandler {
handler()
AppDelegate.shared.backgroundCompletionHandler = nil
}
}
}
}Part 4: Location Efficiency APIs
第四部分:位置效率API
CLLocationManager Configuration
CLLocationManager配置
swift
import CoreLocation
class LocationService: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
func configure() {
manager.delegate = self
// Use appropriate accuracy
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
// Reduce update frequency
manager.distanceFilter = 100 // Update every 100 meters
// Allow indicator pause when stationary
manager.pausesLocationUpdatesAutomatically = true
// For background updates (if needed)
manager.allowsBackgroundLocationUpdates = true
manager.showsBackgroundLocationIndicator = true
}
func startTracking() {
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
func startSignificantChangeTracking() {
// Much more energy efficient for background
manager.startMonitoringSignificantLocationChanges()
}
func stopTracking() {
manager.stopUpdatingLocation()
manager.stopMonitoringSignificantLocationChanges()
}
}swift
import CoreLocation
class LocationService: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
func configure() {
manager.delegate = self
// 使用合适的精度
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
// 降低更新频率
manager.distanceFilter = 100 // 每移动100米更新一次
// 静止时允许暂停位置更新
manager.pausesLocationUpdatesAutomatically = true
// 如需后台更新
manager.allowsBackgroundLocationUpdates = true
manager.showsBackgroundLocationIndicator = true
}
func startTracking() {
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
func startSignificantChangeTracking() {
// 后台使用时更节能
manager.startMonitoringSignificantLocationChanges()
}
func stopTracking() {
manager.stopUpdatingLocation()
manager.stopMonitoringSignificantLocationChanges()
}
}iOS 26+ CLLocationUpdate (Modern Async API)
iOS 26+ CLLocationUpdate(现代异步API)
swift
import CoreLocation
func trackLocation() async throws {
for try await update in CLLocationUpdate.liveUpdates() {
// Check if device became stationary
if update.stationary {
// System pauses updates automatically
// Consider switching to region monitoring
break
}
if let location = update.location {
handleLocation(location)
}
}
}swift
import CoreLocation
func trackLocation() async throws {
for try await update in CLLocationUpdate.liveUpdates() {
// 检查设备是否静止
if update.stationary {
// 系统会自动暂停更新
// 考虑切换到区域监控
break
}
if let location = update.location {
handleLocation(location)
}
}
}CLMonitor for Significant Changes
CLMonitor用于显著位置变化
swift
import CoreLocation
func setupRegionMonitoring() async {
let monitor = CLMonitor("significant-changes")
// Add condition to monitor
let condition = CLMonitor.CircularGeographicCondition(
center: currentLocation.coordinate,
radius: 500 // 500 meter radius
)
await monitor.add(condition, identifier: "home-region")
// React to events
for try await event in monitor.events {
switch event.state {
case .satisfied:
// Entered region
handleRegionEntry()
case .unsatisfied:
// Exited region
handleRegionExit()
default:
break
}
}
}swift
import CoreLocation
func setupRegionMonitoring() async {
let monitor = CLMonitor("significant-changes")
// 添加要监控的条件
let condition = CLMonitor.CircularGeographicCondition(
center: currentLocation.coordinate,
radius: 500 // 500米半径
)
await monitor.add(condition, identifier: "home-region")
// 响应事件
for try await event in monitor.events {
switch event.state {
case .satisfied:
// 进入区域
handleRegionEntry()
case .unsatisfied:
// 离开区域
handleRegionExit()
default:
break
}
}
}Location Accuracy Options
位置精度选项
| Constant | Accuracy | Battery Impact | Use Case |
|---|---|---|---|
| ~1m | Extreme | Turn-by-turn only |
| ~10m | Very High | Fitness tracking |
| ~10m | High | Precise positioning |
| ~100m | Medium | Store locators |
| ~1km | Low | Weather, general |
| ~3km | Very Low | Regional content |
| 常量 | 精度 | 电池影响 | 使用场景 |
|---|---|---|---|
| ~1米 | 极高 | 仅用于逐向导航 |
| ~10米 | 很高 | 健身追踪 |
| ~10米 | 高 | 精确定位 |
| ~100米 | 中等 | 商店定位器 |
| ~1公里 | 低 | 天气、通用场景 |
| ~3公里 | 极低 | 区域内容推送 |
Part 5: Background Execution APIs
第五部分:后台执行API
beginBackgroundTask (Short Tasks)
beginBackgroundTask(短任务)
swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
func applicationDidEnterBackground(_ application: UIApplication) {
backgroundTask = application.beginBackgroundTask(withName: "Save State") {
// Expiration handler - clean up
self.endBackgroundTask()
}
// Perform quick work
saveState()
// End immediately when done
endBackgroundTask()
}
private func endBackgroundTask() {
guard backgroundTask != .invalid else { return }
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
}swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
func applicationDidEnterBackground(_ application: UIApplication) {
backgroundTask = application.beginBackgroundTask(withName: "保存状态") {
// 过期处理程序 - 清理资源
self.endBackgroundTask()
}
// 执行快速任务
saveState()
// 完成后立即结束
endBackgroundTask()
}
private func endBackgroundTask() {
guard backgroundTask != .invalid else { return }
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
}BGAppRefreshTask
BGAppRefreshTask
swift
import BackgroundTasks
// Register at app launch
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
// Schedule refresh
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 min
try? BGTaskScheduler.shared.submit(request)
}
// Handle refresh
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh() // Schedule next refresh
let fetchTask = Task {
do {
let hasNewData = try await fetchLatestData()
task.setTaskCompleted(success: hasNewData)
} catch {
task.setTaskCompleted(success: false)
}
}
task.expirationHandler = {
fetchTask.cancel()
}
}swift
import BackgroundTasks
// 在应用启动时注册
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { task in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
// 调度刷新任务
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15分钟后
try? BGTaskScheduler.shared.submit(request)
}
// 处理刷新任务
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh() // 调度下一次刷新
let fetchTask = Task {
do {
let hasNewData = try await fetchLatestData()
task.setTaskCompleted(success: hasNewData)
} catch {
task.setTaskCompleted(success: false)
}
}
task.expirationHandler = {
fetchTask.cancel()
}
}BGProcessingTask
BGProcessingTask
swift
import BackgroundTasks
// Register
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.maintenance",
using: nil
) { task in
self.handleMaintenance(task: task as! BGProcessingTask)
}
// Schedule with requirements
func scheduleMaintenance() {
let request = BGProcessingTaskRequest(identifier: "com.app.maintenance")
request.requiresNetworkConnectivity = true
request.requiresExternalPower = true // Only when charging
try? BGTaskScheduler.shared.submit(request)
}
// Handle
func handleMaintenance(task: BGProcessingTask) {
let operation = MaintenanceOperation()
task.expirationHandler = {
operation.cancel()
}
operation.completionBlock = {
task.setTaskCompleted(success: !operation.isCancelled)
}
OperationQueue.main.addOperation(operation)
}swift
import BackgroundTasks
// 注册任务
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.maintenance",
using: nil
) { task in
self.handleMaintenance(task: task as! BGProcessingTask)
}
// 带需求条件调度任务
func scheduleMaintenance() {
let request = BGProcessingTaskRequest(identifier: "com.app.maintenance")
request.requiresNetworkConnectivity = true
request.requiresExternalPower = true // 仅在充电时执行
try? BGTaskScheduler.shared.submit(request)
}
// 处理维护任务
func handleMaintenance(task: BGProcessingTask) {
let operation = MaintenanceOperation()
task.expirationHandler = {
operation.cancel()
}
operation.completionBlock = {
task.setTaskCompleted(success: !operation.isCancelled)
}
OperationQueue.main.addOperation(operation)
}iOS 26+ BGContinuedProcessingTask
iOS 26+ BGContinuedProcessingTask
From WWDC25-227: Continue user-initiated tasks with system UI.
swift
import BackgroundTasks
// Info.plist: Add identifier to BGTaskSchedulerPermittedIdentifiers
// "com.app.export" or "com.app.exports.*" for wildcards
// Register handler (can be dynamic, not just at launch)
func setupExportHandler() {
BGTaskScheduler.shared.register("com.app.export") { task in
let continuedTask = task as! BGContinuedProcessingTask
var shouldContinue = true
continuedTask.expirationHandler = {
shouldContinue = false
}
// Report progress
continuedTask.progress.totalUnitCount = 100
continuedTask.progress.completedUnitCount = 0
// Perform work
for i in 0..<100 {
guard shouldContinue else { break }
performExportStep(i)
continuedTask.progress.completedUnitCount = Int64(i + 1)
}
continuedTask.setTaskCompleted(success: shouldContinue)
}
}
// Submit request
func startExport() {
let request = BGContinuedProcessingTaskRequest(
identifier: "com.app.export",
title: "Exporting Photos",
subtitle: "0 of 100 photos"
)
// Submission strategy
request.strategy = .fail // Fail if can't start immediately
// or default: queue if can't start
do {
try BGTaskScheduler.shared.submit(request)
} catch {
// Handle submission failure
showExportNotAvailable()
}
}来自WWDC25-227:通过系统UI继续用户发起的任务。
swift
import BackgroundTasks
// Info.plist: 将标识符添加到BGTaskSchedulerPermittedIdentifiers
// "com.app.export" 或 "com.app.exports.*" 用于通配符
// 注册处理程序(可动态注册,无需仅在启动时注册)
func setupExportHandler() {
BGTaskScheduler.shared.register("com.app.export") { task in
let continuedTask = task as! BGContinuedProcessingTask
var shouldContinue = true
continuedTask.expirationHandler = {
shouldContinue = false
}
// 报告进度
continuedTask.progress.totalUnitCount = 100
continuedTask.progress.completedUnitCount = 0
// 执行任务
for i in 0..<100 {
guard shouldContinue else { break }
performExportStep(i)
continuedTask.progress.completedUnitCount = Int64(i + 1)
}
continuedTask.setTaskCompleted(success: shouldContinue)
}
}
// 提交任务请求
func startExport() {
let request = BGContinuedProcessingTaskRequest(
identifier: "com.app.export",
title: "导出照片",
subtitle: "0/100张照片"
)
// 提交策略
request.strategy = .fail // 若无法立即启动则失败
// 或默认:若无法立即启动则加入队列
do {
try BGTaskScheduler.shared.submit(request)
} catch {
// 处理提交失败
showExportNotAvailable()
}
}EMRCA Principles (from WWDC25-227)
EMRCA原则(来自WWDC25-227)
Background tasks must be:
| Principle | Meaning | Implementation |
|---|---|---|
| Efficient | Lightweight, purpose-driven | Do one thing well |
| Minimal | Keep work to minimum | Don't expand scope |
| Resilient | Save progress, handle expiration | Checkpoint frequently |
| Courteous | Honor preferences | Check Low Power Mode |
| Adaptive | Work with system | Don't fight constraints |
后台任务必须遵循:
| 原则 | 含义 | 实现方式 |
|---|---|---|
| Efficient(高效) | 轻量、目标明确 | 专注完成一件事 |
| Minimal(最小化) | 将工作量降至最低 | 不扩大任务范围 |
| Resilient(弹性) | 保存进度、处理过期 | 频繁检查点 |
| Courteous(友好) | 尊重用户偏好 | 检查低电量模式 |
| Adaptive(自适应) | 配合系统工作 | 不违背系统限制 |
Part 6: Display & GPU Efficiency APIs
第六部分:显示屏与GPU效率API
Dark Mode Support
深色模式支持
swift
// Check current appearance
let isDarkMode = traitCollection.userInterfaceStyle == .dark
// React to appearance changes
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
updateColorsForAppearance()
}
}
// Use dynamic colors
let dynamicColor = UIColor { traitCollection in
switch traitCollection.userInterfaceStyle {
case .dark:
return UIColor.black // OLED: True black = pixels off = 0 power
default:
return UIColor.white
}
}swift
// 检查当前外观模式
let isDarkMode = traitCollection.userInterfaceStyle == .dark
// 响应外观变化
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
updateColorsForAppearance()
}
}
// 使用动态颜色
let dynamicColor = UIColor { traitCollection in
switch traitCollection.userInterfaceStyle {
case .dark:
return UIColor.black // OLED:纯黑 = 像素关闭 = 0功耗
default:
return UIColor.white
}
}Frame Rate Control with CADisplayLink
使用CADisplayLink控制帧率
From WWDC22-10083:
swift
class AnimationController {
private var displayLink: CADisplayLink?
func startAnimation() {
displayLink = CADisplayLink(target: self, selector: #selector(update))
// Control frame rate
displayLink?.preferredFrameRateRange = CAFrameRateRange(
minimum: 10, // Minimum acceptable
maximum: 30, // Maximum needed
preferred: 30 // Ideal rate
)
displayLink?.add(to: .current, forMode: .default)
}
@objc private func update(_ displayLink: CADisplayLink) {
// Update animation
updateAnimationFrame()
}
func stopAnimation() {
displayLink?.invalidate()
displayLink = nil
}
}来自WWDC22-10083:
swift
class AnimationController {
private var displayLink: CADisplayLink?
func startAnimation() {
displayLink = CADisplayLink(target: self, selector: #selector(update))
// 控制帧率
displayLink?.preferredFrameRateRange = CAFrameRateRange(
minimum: 10, // 最低可接受帧率
maximum: 30, // 所需最高帧率
preferred: 30 // 理想帧率
)
displayLink?.add(to: .current, forMode: .default)
}
@objc private func update(_ displayLink: CADisplayLink) {
// 更新动画帧
updateAnimationFrame()
}
func stopAnimation() {
displayLink?.invalidate()
displayLink = nil
}
}Stop Animations When Not Visible
不可见时停止动画
swift
class AnimatedViewController: UIViewController {
private var animator: UIViewPropertyAnimator?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startAnimations()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopAnimations() // Critical for energy
}
private func stopAnimations() {
animator?.stopAnimation(true)
animator = nil
}
}swift
class AnimatedViewController: UIViewController {
private var animator: UIViewPropertyAnimator?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startAnimations()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopAnimations() // 对节能至关重要
}
private func stopAnimations() {
animator?.stopAnimation(true)
animator = nil
}
}Part 7: Disk I/O Efficiency APIs
第七部分:磁盘I/O效率API
Batch Writes
批量写入
swift
// BAD: Multiple small writes
for item in items {
let data = try JSONEncoder().encode(item)
try data.write(to: fileURL) // Writes each item separately
}
// GOOD: Single batched write
let allData = try JSONEncoder().encode(items)
try allData.write(to: fileURL) // One write operationswift
// 不良实践:多次小写入
for item in items {
let data = try JSONEncoder().encode(item)
try data.write(to: fileURL) // 单独写入每个条目
}
// 良好实践:单次批量写入
let allData = try JSONEncoder().encode(items)
try allData.write(to: fileURL) // 一次写入操作SQLite WAL Mode
SQLite WAL模式
swift
import SQLite3
// Enable Write-Ahead Logging
var db: OpaquePointer?
sqlite3_open(dbPath, &db)
var statement: OpaquePointer?
sqlite3_prepare_v2(db, "PRAGMA journal_mode=WAL", -1, &statement, nil)
sqlite3_step(statement)
sqlite3_finalize(statement)swift
import SQLite3
// 启用预写日志
var db: OpaquePointer?
sqlite3_open(dbPath, &db)
var statement: OpaquePointer?
sqlite3_prepare_v2(db, "PRAGMA journal_mode=WAL", -1, &statement, nil)
sqlite3_step(statement)
sqlite3_finalize(statement)XCTStorageMetric for Testing
使用XCTStorageMetric进行测试
swift
import XCTest
class DiskWriteTests: XCTestCase {
func testDiskWritePerformance() {
measure(metrics: [XCTStorageMetric()]) {
// Code that writes to disk
saveUserData()
}
}
}swift
import XCTest
class DiskWriteTests: XCTestCase {
func testDiskWritePerformance() {
measure(metrics: [XCTStorageMetric()]) {
// 写入磁盘的代码
saveUserData()
}
}
}Part 8: Low Power Mode & Thermal Response APIs
第八部分:低电量模式与热状态响应API
Low Power Mode Detection
低电量模式检测
swift
import Foundation
class PowerStateManager {
private var cancellables = Set<AnyCancellable>()
init() {
// Check initial state
updateForPowerState()
// Observe changes
NotificationCenter.default.publisher(
for: .NSProcessInfoPowerStateDidChange
)
.sink { [weak self] _ in
self?.updateForPowerState()
}
.store(in: &cancellables)
}
private func updateForPowerState() {
if ProcessInfo.processInfo.isLowPowerModeEnabled {
reduceEnergyUsage()
} else {
restoreNormalOperation()
}
}
private func reduceEnergyUsage() {
// Increase timer intervals
// Reduce animation frame rates
// Defer network requests
// Stop location updates if not critical
// Reduce refresh frequency
}
}swift
import Foundation
class PowerStateManager {
private var cancellables = Set<AnyCancellable>()
init() {
// 检查初始状态
updateForPowerState()
// 监听状态变化
NotificationCenter.default.publisher(
for: .NSProcessInfoPowerStateDidChange
)
.sink { [weak self] _ in
self?.updateForPowerState()
}
.store(in: &cancellables)
}
private func updateForPowerState() {
if ProcessInfo.processInfo.isLowPowerModeEnabled {
reduceEnergyUsage()
} else {
restoreNormalOperation()
}
}
private func reduceEnergyUsage() {
// 增加计时器间隔
// 降低动画帧率
// 延迟网络请求
// 若非关键则停止位置更新
// 降低刷新频率
}
}Thermal State Response
热状态响应
swift
import Foundation
class ThermalManager {
init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(thermalStateChanged),
name: ProcessInfo.thermalStateDidChangeNotification,
object: nil
)
}
@objc private func thermalStateChanged() {
switch ProcessInfo.processInfo.thermalState {
case .nominal:
// Normal operation
restoreFullFunctionality()
case .fair:
// Slightly elevated, minor reduction
reduceNonEssentialWork()
case .serious:
// Significant reduction needed
suspendBackgroundTasks()
reduceAnimationQuality()
case .critical:
// Maximum reduction
minimizeAllActivity()
showThermalWarningIfAppropriate()
@unknown default:
break
}
}
}swift
import Foundation
class ThermalManager {
init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(thermalStateChanged),
name: ProcessInfo.thermalStateDidChangeNotification,
object: nil
)
}
@objc private func thermalStateChanged() {
switch ProcessInfo.processInfo.thermalState {
case .nominal:
// 正常运行
restoreFullFunctionality()
case .fair:
// 温度略有升高,轻微降低负载
reduceNonEssentialWork()
case .serious:
// 需要显著降低负载
suspendBackgroundTasks()
reduceAnimationQuality()
case .critical:
// 最大限度降低负载
minimizeAllActivity()
showThermalWarningIfAppropriate()
@unknown default:
break
}
}
}Part 9: MetricKit Monitoring APIs
第九部分:MetricKit监控API
Basic Setup
基础设置
swift
import MetricKit
class MetricsManager: NSObject, MXMetricManagerSubscriber {
static let shared = MetricsManager()
func startMonitoring() {
MXMetricManager.shared.add(self)
}
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
processPayload(payload)
}
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
processDiagnostic(payload)
}
}
}swift
import MetricKit
class MetricsManager: NSObject, MXMetricManagerSubscriber {
static let shared = MetricsManager()
func startMonitoring() {
MXMetricManager.shared.add(self)
}
func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
processPayload(payload)
}
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
processDiagnostic(payload)
}
}
}Processing Energy Metrics
处理能源指标
swift
func processPayload(_ payload: MXMetricPayload) {
// CPU metrics
if let cpu = payload.cpuMetrics {
let foregroundTime = cpu.cumulativeCPUTime
let backgroundTime = cpu.cumulativeCPUInstructions
logMetric("cpu_foreground", value: foregroundTime)
}
// Location metrics
if let location = payload.locationActivityMetrics {
let backgroundLocationTime = location.cumulativeBackgroundLocationTime
logMetric("background_location_seconds", value: backgroundLocationTime)
}
// Network metrics
if let network = payload.networkTransferMetrics {
let cellularUpload = network.cumulativeCellularUpload
let cellularDownload = network.cumulativeCellularDownload
let wifiUpload = network.cumulativeWiFiUpload
let wifiDownload = network.cumulativeWiFiDownload
logMetric("cellular_upload", value: cellularUpload)
logMetric("cellular_download", value: cellularDownload)
}
// Disk metrics
if let disk = payload.diskIOMetrics {
let writes = disk.cumulativeLogicalWrites
logMetric("disk_writes", value: writes)
}
// GPU metrics
if let gpu = payload.gpuMetrics {
let gpuTime = gpu.cumulativeGPUTime
logMetric("gpu_time", value: gpuTime)
}
}swift
func processPayload(_ payload: MXMetricPayload) {
// CPU指标
if let cpu = payload.cpuMetrics {
let foregroundTime = cpu.cumulativeCPUTime
let backgroundTime = cpu.cumulativeCPUInstructions
logMetric("cpu_foreground", value: foregroundTime)
}
// 位置指标
if let location = payload.locationActivityMetrics {
let backgroundLocationTime = location.cumulativeBackgroundLocationTime
logMetric("background_location_seconds", value: backgroundLocationTime)
}
// 网络指标
if let network = payload.networkTransferMetrics {
let cellularUpload = network.cumulativeCellularUpload
let cellularDownload = network.cumulativeCellularDownload
let wifiUpload = network.cumulativeWiFiUpload
let wifiDownload = network.cumulativeWiFiDownload
logMetric("cellular_upload", value: cellularUpload)
logMetric("cellular_download", value: cellularDownload)
}
// 磁盘指标
if let disk = payload.diskIOMetrics {
let writes = disk.cumulativeLogicalWrites
logMetric("disk_writes", value: writes)
}
// GPU指标
if let gpu = payload.gpuMetrics {
let gpuTime = gpu.cumulativeGPUTime
logMetric("gpu_time", value: gpuTime)
}
}Xcode Organizer Integration
Xcode Organizer集成
View field metrics in Xcode:
- Window → Organizer
- Select your app
- Click "Battery Usage" in sidebar
- Compare versions, filter by device/OS
Categories shown:
- Audio
- Networking
- Processing (CPU + GPU)
- Display
- Bluetooth
- Location
- Camera
- Torch
- NFC
- Other
在Xcode中查看现场指标:
- 窗口 → Organizer
- 选择你的应用
- 点击侧边栏中的“电池使用情况”
- 对比版本,按设备/系统版本筛选
显示的分类:
- 音频
- 网络
- 处理(CPU + GPU)
- 显示屏
- 蓝牙
- 位置
- 相机
- 手电筒
- NFC
- 其他
Part 10: Push Notifications APIs
第十部分:推送通知API
Alert Notifications Setup
提醒通知设置
From WWDC20-10095:
swift
import UserNotifications
class NotificationManager: NSObject, UNUserNotificationCenterDelegate {
func setup() {
UNUserNotificationCenter.current().delegate = self
UIApplication.shared.registerForRemoteNotifications()
}
func requestPermission() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge]
) { granted, error in
print("Permission granted: \(granted)")
}
}
}
// AppDelegate
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
sendTokenToServer(token)
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("Failed to register: \(error)")
}来自WWDC20-10095:
swift
import UserNotifications
class NotificationManager: NSObject, UNUserNotificationCenterDelegate {
func setup() {
UNUserNotificationCenter.current().delegate = self
UIApplication.shared.registerForRemoteNotifications()
}
func requestPermission() {
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .badge]
) { granted, error in
print("权限已授予:\(granted)")
}
}
}
// AppDelegate
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
sendTokenToServer(token)
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("注册失败:\(error)")
}Background Push Notifications
后台推送通知
swift
// Handle background notification
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
// Check for content-available flag
guard let aps = userInfo["aps"] as? [String: Any],
aps["content-available"] as? Int == 1 else {
completionHandler(.noData)
return
}
Task {
do {
let hasNewData = try await fetchLatestContent()
completionHandler(hasNewData ? .newData : .noData)
} catch {
completionHandler(.failed)
}
}
}swift
// 处理后台通知
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
// 检查content-available标志
guard let aps = userInfo["aps"] as? [String: Any],
aps["content-available"] as? Int == 1 else {
completionHandler(.noData)
return
}
Task {
do {
let hasNewData = try await fetchLatestContent()
completionHandler(hasNewData ? .newData : .noData)
} catch {
completionHandler(.failed)
}
}
}Server Payload Examples
服务器负载示例
json
// Alert notification (user-visible)
{
"aps": {
"alert": {
"title": "New Message",
"body": "You have a new message from John"
},
"sound": "default",
"badge": 1
},
"message_id": "12345"
}
// Background notification (silent)
{
"aps": {
"content-available": 1
},
"update_type": "new_content"
}json
// 提醒通知(对用户可见)
{
"aps": {
"alert": {
"title": "新消息",
"body": "你收到了来自John的新消息"
},
"sound": "default",
"badge": 1
},
"message_id": "12345"
}
// 后台通知(静默)
{
"aps": {
"content-available": 1
},
"update_type": "new_content"
}Push Priority Headers
推送优先级头部
| Priority | Header | Use Case |
|---|---|---|
| High (10) | | Time-sensitive alerts |
| Low (5) | | Deferrable updates |
Energy tip: Use priority 5 for all non-urgent notifications. System batches low-priority pushes for energy efficiency.
| 优先级 | 头部 | 使用场景 |
|---|---|---|
| 高(10) | | 时间敏感的提醒 |
| 低(5) | | 可延迟的更新 |
节能提示:所有非紧急通知使用优先级5。系统会批量处理低优先级推送以提升能源效率。
Troubleshooting Checklist
故障排查清单
Issue: App at Top of Battery Settings
问题:应用在电池设置中排名靠前
- Run Power Profiler to identify dominant subsystem
- Check for timers without tolerance
- Check for polling patterns
- Check for continuous location
- Check for background audio session
- Verify BGTasks complete promptly
- 运行Power Profiler识别主要消耗子系统
- 检查无容差的计时器
- 检查轮询模式
- 检查持续的位置更新
- 检查后台音频会话
- 验证BGTasks是否及时完成
Issue: Device Gets Hot
问题:设备发热
- Check GPU Power Impact for sustained high values
- Look for continuous animations
- Check for blur effects over dynamic content
- Verify Metal frame limiting
- Check CPU for tight loops
- 检查GPU功耗影响是否持续偏高
- 查找持续运行的动画
- 检查动态内容上的模糊效果
- 验证Metal帧率限制
- 检查CPU是否存在死循环
Issue: Background Battery Drain
问题:后台电池消耗
- Audit background modes in Info.plist
- Verify audio session deactivated when not playing
- Check location accuracy and stop calls
- Verify beginBackgroundTask calls end promptly
- Review BGTask scheduling
- 审核Info.plist中的后台模式
- 验证音频会话在不播放时是否已停用
- 检查位置精度和停止调用
- 验证beginBackgroundTask是否及时调用end
- 审查BGTask调度
Issue: High Cellular Usage
问题:蜂窝网络使用过高
- Check allowsExpensiveNetworkAccess setting
- Verify discretionary flag on background downloads
- Look for polling patterns
- Check for large automatic downloads
- 检查allowsExpensiveNetworkAccess设置
- 验证后台下载的discretionary标志
- 查找轮询模式
- 检查大型自动下载
Expert Review Checklist
专家评审清单
Timers (10 items)
计时器(10项)
- Tolerance ≥10% on all timers
- Timers invalidated in deinit
- No timers running when app backgrounded
- Using Combine Timer where possible
- No sub-second intervals without justification
- Event-driven alternatives considered
- No synchronization via timer polling
- Timer invalidated before creating new one
- Repeating timers have clear stop condition
- Background timer usage justified
- 所有计时器容差≥10%
- 计时器在deinit中失效
- 应用后台时无计时器运行
- 尽可能使用Combine Timer
- 无正当理由不使用亚秒级间隔
- 考虑过事件驱动替代方案
- 无通过计时器轮询进行同步的情况
- 创建新计时器前先失效旧计时器
- 重复计时器有明确的停止条件
- 后台计时器使用有合理理由
Network (10 items)
网络(10项)
- waitsForConnectivity = true
- allowsExpensiveNetworkAccess appropriate
- allowsConstrainedNetworkAccess appropriate
- Non-urgent downloads use discretionary
- Push notifications instead of polling
- Requests batched where possible
- Payloads compressed
- Background URLSession for large transfers
- Retry logic has exponential backoff
- Connection reuse via single URLSession
- waitsForConnectivity = true
- allowsExpensiveNetworkAccess设置合理
- allowsConstrainedNetworkAccess设置合理
- 非紧急下载使用可自主调度模式
- 使用推送通知而非轮询
- 请求已尽可能批量处理
- 负载已压缩
- 大型传输使用后台URLSession
- 重试逻辑具备指数退避
- 通过单个URLSession复用连接
Location (10 items)
位置(10项)
- Accuracy appropriate for use case
- distanceFilter set
- Updates stopped when not needed
- pausesLocationUpdatesAutomatically = true
- Background location only if essential
- Significant-change for background
- CLMonitor for region monitoring
- Location permission matches actual need
- Stationary detection utilized
- Location icon explained to users
- 精度符合使用场景需求
- 已设置distanceFilter
- 不需要时已停止更新
- pausesLocationUpdatesAutomatically = true
- 仅在必要时使用后台位置
- 后台使用显著位置变化监控
- 使用CLMonitor进行区域监控
- 位置权限与实际需求匹配
- 已利用静止检测
- 已向用户说明位置图标含义
Background Execution (10 items)
后台执行(10项)
- endBackgroundTask called promptly
- Expiration handlers implemented
- BGTasks use requiresExternalPower when possible
- EMRCA principles followed
- Background modes limited to needed
- Audio session deactivated when idle
- Progress saved incrementally
- Tasks complete within time limits
- Low Power Mode checked before heavy work
- Thermal state monitored
- endBackgroundTask及时调用
- 已实现过期处理程序
- 可能时BGTasks使用requiresExternalPower
- 遵循EMRCA原则
- 后台模式仅限于必要的使用
- 音频会话在闲置时已停用
- 已逐步保存进度
- 任务在时间限制内完成
- 执行繁重工作前检查低电量模式
- 已监控热状态
Display/GPU (10 items)
显示屏/GPU(10项)
- Dark Mode supported
- Animations stop when view hidden
- Frame rates appropriate for content
- Secondary animations lower priority
- Blur effects minimized
- Metal has frame limiting
- Brightness-independent design
- No hidden animations consuming power
- GPU-intensive work has visibility checks
- ProMotion considered in frame rate decisions
- 支持深色模式
- 视图不可见时停止动画
- 帧率与内容需求匹配
- 次要动画优先级较低
- 模糊效果已最小化
- Metal已设置帧率限制
- 设计与亮度无关
- 无隐藏动画消耗电力
- GPU密集型工作有可见性检查
- 帧率决策已考虑ProMotion
WWDC Session Reference
WWDC会话参考
| Session | Year | Topic |
|---|---|---|
| 226 | 2025 | Power Profiler workflow, on-device tracing |
| 227 | 2025 | BGContinuedProcessingTask, EMRCA principles |
| 10083 | 2022 | Dark Mode, frame rates, deferral |
| 10095 | 2020 | Push notifications primer |
| 707 | 2019 | Background execution advances |
| 417 | 2019 | Battery life, MetricKit |
Last Updated: 2025-12-26
Platforms: iOS 26+, iPadOS 26+
| 会话编号 | 年份 | 主题 |
|---|---|---|
| 226 | 2025 | Power Profiler工作流、设备端追踪 |
| 227 | 2025 | BGContinuedProcessingTask、EMRCA原则 |
| 10083 | 2022 | 深色模式、帧率、延迟处理 |
| 10095 | 2020 | 推送通知入门 |
| 707 | 2019 | 后台执行进阶 |
| 417 | 2019 | 电池续航、MetricKit |
最后更新:2025-12-26
支持平台:iOS 26+, iPadOS 26+