axiom-energy-ref について
iOSアプリのエネルギー消費を抑えるためのAPIリファレンスです。AppleのドキュメントやWWDCの例を基に、計測とタイマー、ネットワーク、位置情報、バックグラウンド処理などの実装を扱います。
主な機能
- 01Instrumentsで電力消費を計測
- 02効率的なタイマーとネットワークを設定
- 03位置情報とバックグラウンド処理を管理
- 04画面、GPU、ディスクの動作を削減
- 05MetricKitでエネルギーを監視
活用シーン
- 01変更前後のアプリ消費電力を比較
- 02ポーリングやタイマーの動作を削減
- 03バッテリー影響に応じて位置情報更新を調整
- 04本番環境のエネルギー指標を追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-energy-refインストール後、Claude Code で/axiom-energy-refを入力して読み込まれたか確認します。
詳細
エネルギー最適化リファレンス
iOS のエネルギー最適化に関する完全な API リファレンスです。WWDC のセッションおよび Apple のドキュメントからのコード例を掲載しています。
関連スキル:axiom-energy(判断ツリー、パターン)、axiom-energy-diag(トラブルシューティング)
パート 1:Power Profiler のワークフロー
Instruments でトレースを記録する
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, Network
重要:ワイヤレスデバッグを使用してください。デバイスをケーブルで充電していると、システムの電力使用量が 0 と表示されます。
デバイス上で記録する(Mac を使用しない場合)
WWDC25-226 より:実環境でトレースを取得します。
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
Power Profiler のメトリクスを解釈する
| レーン | 意味 | 値が高い場合に示すもの |
|---|---|---|
| システム電力 | 全体的なバッテリー消費率 | 一般的なエネルギー消費 |
| CPU 電力インパクト | プロセッサーの動作スコア | 計算、タイマー、解析 |
| GPU 電力インパクト | グラフィックス描画スコア | アニメーション、ぼかし、Metal |
| ディスプレイ電力インパクト | 画面の電力使用量 | 明るさ、コンテンツの種類 |
| ネットワーク電力インパクト | 無線の動作スコア | リクエスト、ダウンロード、ポーリング |
重要なポイント:値は比較用のスコアであり、絶対的な測定値ではありません。同じデバイスで、最適化の前後のトレースを比較してください。
最適化前後の比較(WWDC25-226 の例)
// 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)
}
}
パート 2:タイマー効率化 API
許容誤差を設定した NSTimer
// 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()
}
Combine のタイマーパブリッシャー
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 タイマーソース(低レベル)
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()
DispatchSourceTimer のライフサイクルの安全性とクラッシュ防止については、
axiom-timer-patternsを参照してください。
タイマーの代わりとなるイベント駆動型の方法
Energy Efficiency Guide より:ポーリングよりもディスパッチソースを優先してください。
// 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()
パート 3:ネットワーク効率化 API
URLSession の構成
// 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)
裁量的なバックグラウンドダウンロード
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
}
}
}
}
パート 4:位置情報効率化 API
CLLocationManager の構成
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()
}
}
iOS 26 以降の CLLocationUpdate(最新の非同期 API)
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)
}
}
}
大幅な変化の検出に使う CLMonitor
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
}
}
}
位置情報の精度オプション
| 定数 | 精度 | バッテリーへの影響 | 用途 |
|---|---|---|---|
kCLLocationAccuracyBestForNavigation | 約 1m | 非常に大きい | ターンバイターン方式の案内のみ |
kCLLocationAccuracyBest | 約 10m | とても大きい | フィットネスの追跡 |
kCLLocationAccuracyNearestTenMeters | 約 10m | 大きい | 高精度な測位 |
kCLLocationAccuracyHundredMeters | 約 100m | 中程度 | 店舗の位置検索 |
kCLLocationAccuracyKilometer | 約 1km | 小さい | 天気、一般用途 |
kCLLocationAccuracyThreeKilometers | 約 3km | 非常に小さい | 地域別コンテンツ |
パート 5:バックグラウンド実行 API
beginBackgroundTask(短時間のタスク)
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
}
}
BGAppRefreshTask
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()
}
}
BGProcessingTask
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)
}
iOS 26 以降の BGContinuedProcessingTask
WWDC25-227 より:システム 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()
}
}
EMRCA の原則(WWDC25-227 より)
バックグラウンドタスクは、次の要件を満たす必要があります。
| 原則 | 意味 | 実装 |
|---|---|---|
| Efficient(効率的) | 軽量で、目的に沿っている | 1 つのことを適切に行う |
| Minimal(最小限) | 作業を最小限に抑える | 対象範囲を広げない |
| Resilient(回復力がある) | 進捗を保存し、期限切れに対処する | 頻繁にチェックポイントを保存する |
| Courteous(配慮がある) | ユーザーの設定を尊重する | 低電力モードを確認する |
| Adaptive(適応性がある) | システムと協調する | 制約に逆らわない |
パート 6:ディスプレイと GPU の効率化 API
ダークモードのサポート
// 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
}
}
CADisplayLink によるフレームレート制御
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
}
}
パート 7:ディスク I/O 効率化 API
書き込みのバッチ処理
// 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
SQLite の WAL モード
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)
テスト用の XCTStorageMetric
import XCTest
class DiskWriteTests: XCTestCase {
func testDiskWritePerformance() {
measure(metrics: [XCTStorageMetric()]) {
// Code that writes to disk
saveUserData()
}
}
}
パート 8:低電力モードと熱状態への対応 API
低電力モードの検出
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
}
}
}
パート 9:MetricKit のモニタリング API
基本設定
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)
}
}
Xcode Organizer との統合
Xcode でフィールド指標を確認します。
- 「ウインドウ」→「Organizer」を選択します
- アプリを選択します
- サイドバーで「バッテリー使用状況」をクリックします
- バージョンを比較し、デバイス/OS で絞り込みます
表示されるカテゴリ:
- オーディオ
- ネットワーク
- 処理(CPU + GPU)
- ディスプレイ
- Bluetooth
- 位置情報
- カメラ
- フラッシュ
- NFC
- その他
パート 10:プッシュ通知 API
アラート通知の設定
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"
}
プッシュ優先度ヘッダー
| 優先度 | ヘッダー | 用途 |
|---|---|---|
| 高(10) | apns-priority: 10 | 時間的制約のあるアラート |
| 低(5) | apns-priority: 5 | 後回しにできるアップデート |
省エネルギーのヒント:緊急でない通知にはすべて優先度 5 を使用します。システムはエネルギー効率を高めるため、低優先度のプッシュ通知をまとめて処理します。
トラブルシューティングのチェックリスト
問題:バッテリー設定でアプリが最上位に表示される
- Power Profiler を実行して、支配的なサブシステムを特定します
- 許容誤差のないタイマーがないか確認します
- ポーリングのパターンがないか確認します
- 位置情報を継続的に使用していないか確認します
- バックグラウンドオーディオセッションがないか確認します
- BGTasks が速やかに完了することを確認します
問題:デバイスが熱くなる
- GPU Power Impact の値が継続的に高くないか確認します
- アニメーションが継続的に実行されていないか確認します
- 動的コンテンツ上でブラー効果を使用していないか確認します
- Metal のフレーム制限を確認します
- CPU でタイトなループがないか確認します
問題:バックグラウンドでバッテリーが消耗する
- Info.plist のバックグラウンドモードを監査します
- 再生していないときにオーディオセッションが無効化されていることを確認します
- 位置情報の精度を確認し、停止呼び出しを確認します
- beginBackgroundTask の呼び出しが速やかに終了することを確認します
- BGTask のスケジュールを確認します
問題:モバイル通信の使用量が多い
- allowsExpensiveNetworkAccess の設定を確認します
- バックグラウンドダウンロードで discretionary フラグが設定されていることを確認します
- ポーリングのパターンがないか確認します
- 大規模な自動ダウンロードがないか確認します
専門家レビューのチェックリスト
タイマー(10 項目)
- すべてのタイマーで許容誤差が 10% 以上
- deinit でタイマーを無効化
- アプリがバックグラウンドにある間、タイマーを実行しない
- 可能な場合は Combine Timer を使用
- 正当な理由なくサブ秒間隔を使用しない
- イベント駆動型の代替手段を検討
- タイマーのポーリングによる同期を行わない
- 新しいタイマーを作成する前に既存のタイマーを無効化
- 繰り返しタイマーに明確な停止条件を設定
- バックグラウンドでのタイマー使用に正当な理由がある
ネットワーク(10 項目)
- waitsForConnectivity = true
- allowsExpensiveNetworkAccess が適切
- allowsConstrainedNetworkAccess が適切
- 緊急でないダウンロードで discretionary を使用
- ポーリングの代わりにプッシュ通知を使用
- 可能な場合はリクエストをまとめる
- ペイロードを圧縮
- 大規模な転送にはバックグラウンド URLSession を使用
- 再試行ロジックに指数バックオフを使用
- 単一の URLSession で接続を再利用
位置情報(10 項目)
- 用途に適した精度を設定
- distanceFilter を設定
- 不要になったらアップデートを停止
- pausesLocationUpdatesAutomatically = true
- バックグラウンドでの位置情報の使用は必須の場合に限る
- バックグラウンドでは Significant-change を使用
- リージョン監視には CLMonitor を使用
- 位置情報の権限が実際の必要性に合っている
- 停止状態の検出を活用
- 位置情報アイコンについてユーザーに説明
バックグラウンド実行(10 項目)
- endBackgroundTask を速やかに呼び出す
- 有効期限切れハンドラーを実装
- 可能な場合は BGTasks で requiresExternalPower を使用
- EMRCA の原則に従う
- バックグラウンドモードを必要なものに限定
- アイドル時にオーディオセッションを無効化
- 進行状況を段階的に保存
- 制限時間内にタスクを完了
- 負荷の高い処理の前に低電力モードを確認
- 熱状態を監視
ディスプレイ/GPU(10 項目)
- ダークモードに対応している
- ビューが非表示になるとアニメーションが停止する
- コンテンツに適したフレームレートになっている
- 付随的なアニメーションの優先度が低く設定されている
- ぼかし効果を最小限に抑えている
- Metal でフレームレートが制限されている
- 画面の明るさに依存しないデザインになっている
- 非表示のアニメーションが電力を消費しない
- GPU 負荷の高い処理に表示状態のチェックがある
- フレームレートの決定で ProMotion を考慮している
WWDC セッションの参考資料
| セッション | 年 | トピック |
|---|---|---|
| 226 | 2025 | Power Profiler のワークフロー、デバイス上でのトレース |
| 227 | 2025 | BGContinuedProcessingTask、EMRCA の原則 |
| 10083 | 2022 | ダークモード、フレームレート、処理の延期 |
| 10095 | 2020 | プッシュ通知の基礎 |
| 707 | 2019 | バックグラウンド実行の進化 |
| 417 | 2019 | バッテリー駆動時間、MetricKit |
最終更新日: 2025-12-26 プラットフォーム: iOS 26+、iPadOS 26+