mcpField
A
axiom-energy-ref

axiom-energy-ref

github.com/charleswiltgen開発1.2k172 回インストール今日更新
リポジトリ

axiom-energy-ref について

iOSアプリのエネルギー消費を抑えるためのAPIリファレンスです。AppleのドキュメントやWWDCの例を基に、計測とタイマー、ネットワーク、位置情報、バックグラウンド処理などの実装を扱います。

主な機能

  1. 01Instrumentsで電力消費を計測
  2. 02効率的なタイマーとネットワークを設定
  3. 03位置情報とバックグラウンド処理を管理
  4. 04画面、GPU、ディスクの動作を削減
  5. 05MetricKitでエネルギーを監視

活用シーン

  1. 01変更前後のアプリ消費電力を比較
  2. 02ポーリングやタイマーの動作を削減
  3. 03バッテリー影響に応じて位置情報更新を調整
  4. 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 でフィールド指標を確認します。

  1. 「ウインドウ」→「Organizer」を選択します
  2. アプリを選択します
  3. サイドバーで「バッテリー使用状況」をクリックします
  4. バージョンを比較し、デバイス/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 セッションの参考資料

セッション年トピック
2262025Power Profiler のワークフロー、デバイス上でのトレース
2272025BGContinuedProcessingTask、EMRCA の原則
100832022ダークモード、フレームレート、処理の延期
100952020プッシュ通知の基礎
7072019バックグラウンド実行の進化
4172019バッテリー駆動時間、MetricKit

最終更新日: 2025-12-26 プラットフォーム: iOS 26+、iPadOS 26+

コメント