axiom-energy-ref by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-energy-refiOS 能耗优化的完整 API 参考,包含来自 WWDC 会议和 Apple 文档的代码示例。
相关技能 : axiom-energy (决策树、模式), axiom-energy-diag (故障排除)
1. 将 iPhone 无线连接到 Xcode
- Xcode → Window → Devices and Simulators
- 为您的设备启用 "Connect via network"
2. 分析您的应用
- Xcode → Product → Profile (Cmd+I)
- 选择 Blank 模板
- 点击 "+" → 添加 "Power Profiler"
- 可选添加 "CPU Profiler" 以进行关联分析
3. 录制
- 从目标下拉菜单中选择您的应用
- 点击 Record (红色按钮)
- 正常使用应用 2-3 分钟
- 点击 Stop
4. 分析
- 展开 Power Profiler 轨道
- 检查每个应用通道:CPU、GPU、Display、Network
重要提示:使用无线调试。当设备通过线缆充电时,系统功耗显示为 0。
来自 WWDC25-226:在真实条件下捕获跟踪记录。
1. 启用开发者模式
设置 → 隐私与安全 → 开发者模式 → 启用
2. 启用性能跟踪
设置 → 开发者 → 性能跟踪 → 启用
将跟踪模式设置为 "Power Profiler"
在应用列表中切换开启您的应用
3. 添加控制中心快捷方式
控制中心 → 点击 "+" → 添加控制 → 性能跟踪
4. 录制
向下轻扫 → 点击性能跟踪图标 → 开始
使用应用(最多可录制 10 小时)
点击性能跟踪图标 → 停止
5. 分享跟踪记录
设置 → 开发者 → 性能跟踪
点击跟踪文件旁边的分享按钮
隔空投送到 Mac 或通过电子邮件发送给开发者
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 通道 | 含义 | 高数值表示 |
|---|---|---|
| 系统功耗 | 总体电池消耗率 | 一般能耗 |
| CPU 功耗影响 | 处理器活动得分 | 计算、计时器、解析 |
| GPU 功耗影响 | 图形渲染得分 | 动画、模糊效果、Metal |
| 显示功耗影响 | 屏幕功耗使用 | 亮度、内容类型 |
| 网络功耗影响 | 无线电活动得分 | 请求、下载、轮询 |
关键见解:数值是用于比较的得分,而非绝对测量值。在同一设备上比较优化前后的跟踪记录。
// 优化前:CPU 功耗影响 = 21
VStack {
ForEach(videos) { video in
VideoCardView(video: video)
}
}
// 优化后:CPU 功耗影响 = 4.3
LazyVStack {
ForEach(videos) { video in
VideoCardView(video: video)
}
}
// 带容差的基本计时器
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()
}
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()
}
}
来自能耗效率指南:
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()
关于 DispatchSourceTimer 的生命周期安全性和崩溃预防,请参阅
axiom-timer-patterns。
来自能耗效率指南:优先使用调度源而非轮询。
// 监控文件变化而非轮询
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()
// 具有节能意识设置的标准配置
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true // 不要立即失败
config.allowsExpensiveNetworkAccess = false // 优先使用 WiFi
config.allowsConstrainedNetworkAccess = false // 尊重低数据模式
let session = URLSession(configuration: config)
来自 WWDC22-10083:
// 用于非紧急下载的后台会话
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()
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
}
}
}
}
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()
}
}
import CoreLocation
func trackLocation() async throws {
for try await update in CLLocationUpdate.liveUpdates() {
// 检查设备是否变为静止状态
if update.stationary {
// 系统自动暂停更新
// 考虑切换到区域监控
break
}
if let location = update.location {
handleLocation(location)
}
}
}
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
}
}
}
| 常量 | 精度 | 电池影响 | 使用场景 |
|---|---|---|---|
kCLLocationAccuracyBestForNavigation | ~1m | 极高 | 仅限逐向导航 |
kCLLocationAccuracyBest | ~10m | 很高 | 健身追踪 |
kCLLocationAccuracyNearestTenMeters | ~10m | 高 | 精确定位 |
kCLLocationAccuracyHundredMeters | ~100m | 中等 | 商店定位器 |
kCLLocationAccuracyKilometer | ~1km | 低 | 天气、一般用途 |
kCLLocationAccuracyThreeKilometers | ~3km | 很低 | 区域内容 |
class AppDelegate: UIResponder, UIApplicationDelegate {
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
func applicationDidEnterBackground(_ application: UIApplication) {
backgroundTask = application.beginBackgroundTask(withName: "Save State") {
// 过期处理程序 - 清理
self.endBackgroundTask()
}
// 执行快速工作
saveState()
// 完成后立即结束
endBackgroundTask()
}
private func endBackgroundTask() {
guard backgroundTask != .invalid else { return }
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
}
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()
}
}
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)
}
来自 WWDC25-227:通过系统 UI 继续用户发起的任务。
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()
}
}
后台任务必须:
| 原则 | 含义 | 实现 |
|---|---|---|
| E fficient 高效 | 轻量级、目的驱动 | 做好一件事 |
| M inimal 最小化 | 将工作量保持在最低限度 | 不要扩大范围 |
| R esilient 弹性 | 保存进度、处理过期 | 频繁检查点 |
| C ourteous 礼貌 | 尊重偏好 | 检查低电量模式 |
| A daptive 自适应 | 与系统协同工作 | 不要对抗约束 |
// 检查当前外观
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
}
}
来自 WWDC22-10083:
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
}
}
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
}
}
// 错误:多次小写入
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) // 一次写入操作
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)
import XCTest
class DiskWriteTests: XCTestCase {
func testDiskWritePerformance() {
measure(metrics: [XCTStorageMetric()]) {
// 写入磁盘的代码
saveUserData()
}
}
}
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() {
// 增加计时器间隔
// 降低动画帧率
// 推迟网络请求
// 如果非关键则停止定位更新
// 降低刷新频率
}
}
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
}
}
}
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)
}
}
}
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 中查看现场指标:
显示的类别:
来自 WWDC20-10095:
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)")
}
// 处理后台通知
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)
}
}
}
// 提醒通知(用户可见)
{
"aps": {
"alert": {
"title": "新消息",
"body": "您有一条来自 John 的新消息"
},
"sound": "default",
"badge": 1
},
"message_id": "12345"
}
// 后台通知(静默)
{
"aps": {
"content-available": 1
},
"update_type": "new_content"
}
| 优先级 | 标头 | 使用场景 |
|---|---|---|
| 高 (10) | apns-priority: 10 | 时间敏感的提醒 |
| 低 (5) | apns-priority: 5 | 可推迟的更新 |
能耗提示:对所有非紧急通知使用优先级 5。系统会为节能而批量处理低优先级推送。
| 会议 | 年份 | 主题 |
|---|---|---|
| 226 | 2025 | 功耗分析器工作流程,设备端跟踪 |
| 227 | 2025 | BGContinuedProcessingTask,EMRCA 原则 |
| 10083 | 2022 | 深色模式,帧率,推迟 |
| 10095 | 2020 | 推送通知入门 |
| 707 | 2019 | 后台执行进展 |
| 417 | 2019 | 电池寿命,MetricKit |
最后更新 : 2025-12-26 平台 : iOS 26+, iPadOS 26+
每周安装数
95
仓库
GitHub 星标数
617
首次出现
Jan 21, 2026
安全审计
安装于
opencode79
codex74
claude-code74
gemini-cli72
cursor72
github-copilot69
Complete API reference for iOS energy optimization, with code examples from WWDC sessions and Apple documentation.
Related skills : axiom-energy (decision trees, patterns), axiom-energy-diag (troubleshooting)
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, Network
Important : Use wireless debugging. When device is charging via cable, system power usage shows 0.
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
| 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.
// 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)
}
}
// 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()
}
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()
}
}
From Energy Efficiency Guide:
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()
For DispatchSourceTimer lifecycle safety and crash prevention, see
axiom-timer-patterns.
From Energy Efficiency Guide: Prefer dispatch sources over polling.
// 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()
// 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)
From WWDC22-10083:
// 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()
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
}
}
}
}
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()
}
}
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)
}
}
}
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
}
}
}
| Constant | Accuracy | Battery Impact | Use Case |
|---|---|---|---|
kCLLocationAccuracyBestForNavigation | ~1m | Extreme | Turn-by-turn only |
kCLLocationAccuracyBest | ~10m | Very High | Fitness tracking |
kCLLocationAccuracyNearestTenMeters | ~10m | High | Precise positioning |
kCLLocationAccuracyHundredMeters | ~100m | Medium |
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
}
}
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()
}
}
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)
}
From WWDC25-227: Continue user-initiated tasks with system UI.
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()
}
}
Background tasks must be:
| Principle | Meaning | Implementation |
|---|---|---|
| E fficient | Lightweight, purpose-driven | Do one thing well |
| M inimal | Keep work to minimum | Don't expand scope |
| R esilient | Save progress, handle expiration | Checkpoint frequently |
| C ourteous | Honor preferences | Check Low Power Mode |
| A daptive | Work with system | Don't fight constraints |
// 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
}
}
From WWDC22-10083:
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
}
}
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
}
}
// 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 operation
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)
import XCTest
class DiskWriteTests: XCTestCase {
func testDiskWritePerformance() {
measure(metrics: [XCTStorageMetric()]) {
// Code that writes to disk
saveUserData()
}
}
}
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
}
}
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
}
}
}
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)
}
}
}
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)
}
}
View field metrics in Xcode:
Categories shown:
From WWDC20-10095:
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)")
}
// 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)
}
}
}
// 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"
}
| Priority | Header | Use Case |
|---|---|---|
| High (10) | apns-priority: 10 | Time-sensitive alerts |
| Low (5) | apns-priority: 5 | Deferrable updates |
Energy tip : Use priority 5 for all non-urgent notifications. System batches low-priority pushes for energy efficiency.
| 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+
Weekly Installs
95
Repository
GitHub Stars
617
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubFailSocketFailSnykPass
Installed on
opencode79
codex74
claude-code74
gemini-cli72
cursor72
github-copilot69
ESLint迁移到Oxlint完整指南:JavaScript/TypeScript项目性能优化工具
1,700 周安装
| Store locators |
kCLLocationAccuracyKilometer | ~1km | Low | Weather, general |
kCLLocationAccuracyThreeKilometers | ~3km | Very Low | Regional content |