# IOS Performance

> iOSアプリのパフォーマンス最適化支援スキル。Instruments活用、メモリ/CPU/GPU使用率分析、アプリ起動時間・バッテリー消費の最適化をサポート。Use when: パフォーマンス問題の調査、アプリ最適化、メモリリーク検出、起動時間短縮、バッテリー消費改善。

- Skill: `caphtech/ios-performance` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add caphtech/ios-performance`
- Raw SKILL.md: https://api.skillmd.com/api/skills/caphtech/ios-performance/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: CAPHTECH (https://skillmd.com/u/caphtech)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/caphtech/ios-performance

---


# iOS Performance（パフォーマンス最適化支援）

## 概要

iOSアプリケーションのパフォーマンス最適化を包括的に支援するスキル。
Instruments等のツールを活用した計測から、具体的な改善施策の実装までをカバーする。

### 対象領域

- **メモリ使用量**: リーク検出、メモリフットプリント最適化
- **CPU使用率**: 処理の効率化、バックグラウンド処理最適化
- **GPU使用率**: レンダリング最適化、フレームレート改善
- **起動時間**: Cold/Warm起動の高速化
- **バッテリー消費**: 電力効率の改善
- **ネットワーク**: 通信効率の最適化

## 実行条件

以下の状況でこのスキルを起動する：

- アプリの動作が遅いと感じる時
- メモリ使用量が高いと報告された時
- バッテリー消費が多いと指摘された時
- App Store Connect でパフォーマンス警告が出た時
- ユーザーからパフォーマンスに関するフィードバックがあった時
- 新機能追加後のパフォーマンス影響を確認したい時
- リリース前のパフォーマンス検証を行う時

## プロセス

### Phase 1: パフォーマンス計測

#### 1.1 計測指標の定義

| 指標 | 目標値 | 計測方法 |
|------|--------|---------|
| 起動時間（Cold） | < 400ms | Instruments / MetricKit |
| 起動時間（Warm） | < 200ms | Instruments / MetricKit |
| メモリ使用量 | < 100MB（基本機能） | Instruments / Xcode Memory Gauge |
| CPU使用率（アイドル） | < 1% | Instruments |
| フレームレート | 60fps / 120fps | Instruments / CADisplayLink |
| バッテリー消費 | 低〜中 | Instruments / MetricKit |

#### 1.2 Instruments の選択

詳細は references/instruments-guide.md を参照。

| 問題 | 推奨 Instrument |
|------|----------------|
| メモリリーク | Leaks |
| メモリ使用量 | Allocations |
| CPU使用率 | Time Profiler |
| UI性能 | Core Animation / Animation Hitches |
| 起動時間 | App Launch |
| バッテリー | Energy Log |
| ネットワーク | Network |

### Phase 2: メモリ最適化

詳細は references/memory-optimization.md を参照。

#### メモリフットプリント削減

```swift
// 画像メモリ最適化
// Bad: 大きな画像をそのまま保持
let image = UIImage(named: "large_image")

// Good: 適切なサイズにリサイズ
func resizedImage(_ image: UIImage, targetSize: CGSize) -> UIImage {
    let renderer = UIGraphicsImageRenderer(size: targetSize)
    return renderer.image { _ in
        image.draw(in: CGRect(origin: .zero, size: targetSize))
    }
}

// Better: ImageIO でメモリ効率的に読み込み
func downsampledImage(at url: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, imageSourceOptions) else {
        return nil
    }
    
    let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
    let downsampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
    ] as CFDictionary
    
    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
        return nil
    }
    
    return UIImage(cgImage: downsampledImage)
}
```

### Phase 3: CPU最適化

詳細は references/cpu-optimization.md を参照。

#### Time Profiler による分析

```swift
// 重い処理の特定と最適化

// Bad: メインスレッドでの重い処理
func processData() {
    let result = heavyComputation(data)  // UI がブロックされる
    updateUI(with: result)
}

// Good: バックグラウンドスレッドで処理
func processData() {
    Task.detached(priority: .userInitiated) {
        let result = heavyComputation(data)
        await MainActor.run {
            updateUI(with: result)
        }
    }
}
```

### Phase 4: GPU/レンダリング最適化

詳細は references/rendering-optimization.md を参照。

#### 4.1 フレームドロップの検出

```swift
// CADisplayLink でフレームレート監視
class FrameRateMonitor {
    private var displayLink: CADisplayLink?
    private var lastTimestamp: CFTimeInterval = 0
    
    func start() {
        displayLink = CADisplayLink(target: self, selector: #selector(tick))
        displayLink?.add(to: .main, forMode: .common)
    }
    
    @objc private func tick(_ link: CADisplayLink) {
        if lastTimestamp > 0 {
            let duration = link.timestamp - lastTimestamp
            let fps = 1.0 / duration
            if fps < 55 {  // 60fps を下回ったら警告
                print("Frame drop detected: \(fps) fps")
            }
        }
        lastTimestamp = link.timestamp
    }
    
    func stop() {
        displayLink?.invalidate()
        displayLink = nil
    }
}
```

#### 4.2 オフスクリーンレンダリングの回避

```swift
// Bad: オフスクリーンレンダリングを発生させる
view.layer.cornerRadius = 10
view.layer.masksToBounds = true
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 2)
view.layer.shadowOpacity = 0.3

// Good: 別レイヤーで影を描画
view.layer.cornerRadius = 10
view.layer.masksToBounds = true

let shadowView = UIView(frame: view.frame)
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0, height: 2)
shadowView.layer.shadowOpacity = 0.3
shadowView.layer.shadowPath = UIBezierPath(
    roundedRect: view.bounds,
    cornerRadius: 10
).cgPath

// Better: SwiftUI の compositingGroup
view
    .cornerRadius(10)
    .compositingGroup()
    .shadow(radius: 5)
```

### Phase 5: 起動時間最適化

詳細は references/launch-optimization.md を参照。

#### 5.1 起動フェーズの理解

```
Cold Launch:
1. dyld: ライブラリ読み込み
2. Runtime: クラス初期化、+load メソッド
3. UIKit: AppDelegate/SceneDelegate 初期化
4. Initial Frame: 最初の画面描画

Warm Launch:
- プロセスは生きているが、バックグラウンドから復帰
- UIの再構築が必要
```

#### 5.2 起動時間計測

```swift
// アプリ起動時間の計測
class AppLaunchTimer {
    static let shared = AppLaunchTimer()
    
    private let processStartTime: CFAbsoluteTime
    
    init() {
        // プロセス起動時刻を取得
        var kinfo = kinfo_proc()
        var size = MemoryLayout<kinfo_proc>.stride
        var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
        sysctl(&mib, u_int(mib.count), &kinfo, &size, nil, 0)
        
        let startTime = kinfo.kp_proc.p_starttime
        processStartTime = CFAbsoluteTime(startTime.tv_sec) + CFAbsoluteTime(startTime.tv_usec) / 1_000_000
    }
    
    func markFirstFrame() {
        let now = CFAbsoluteTimeGetCurrent()
        let launchTime = now - processStartTime
        print("App launch time: \(launchTime * 1000) ms")
    }
}

// AppDelegate で使用
func application(_ application: UIApplication, 
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 最初のフレーム描画後に計測
    DispatchQueue.main.async {
        AppLaunchTimer.shared.markFirstFrame()
    }
    return true
}
```

#### 5.3 起動最適化テクニック

```swift
// 1. 遅延初期化
class HeavyService {
    static let shared = HeavyService()  // lazy by default
    
    private init() {
        // 重い初期化処理
    }
}

// 2. バックグラウンドでの初期化
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // 起動に必須な処理のみメインスレッドで
    setupCriticalServices()
    
    // 非必須の処理はバックグラウンドで
    DispatchQueue.global(qos: .utility).async {
        self.setupAnalytics()
        self.warmUpCache()
        self.preloadData()
    }
    
    return true
}

// 3. 静的初期化の回避
// Bad: グローバル変数の重い初期化
let heavyFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.locale = Locale(identifier: "ja_JP")
    formatter.dateStyle = .full
    return formatter
}()

// Good: 必要時に初期化
class DateFormatterProvider {
    private var _formatter: DateFormatter?
    
    var formatter: DateFormatter {
        if _formatter == nil {
            let f = DateFormatter()
            f.locale = Locale(identifier: "ja_JP")
            f.dateStyle = .full
            _formatter = f
        }
        return _formatter!
    }
}
```

### Phase 6: バッテリー最適化

詳細は references/battery-optimization.md を参照。

#### 6.1 バッテリー消費の主要因

| 要因 | 影響度 | 対策 |
|------|--------|------|
| 位置情報 | 高 | 精度を下げる、バックグラウンド更新を制限 |
| ネットワーク | 高 | バッチ処理、適切なタイミングで通信 |
| CPU | 中 | 処理の効率化、アイドル時の処理削減 |
| GPU | 中 | 不要なアニメーション削減 |
| センサー | 低〜中 | 必要時のみ使用 |

#### 6.2 位置情報の最適化

```swift
// Bad: 常に高精度
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()

// Good: 用途に応じた精度
// ナビゲーション時
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation

// 大まかな位置のみ必要な場合
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer

// 重要な位置変化のみ
locationManager.startMonitoringSignificantLocationChanges()
```

#### 6.3 ネットワーク最適化

```swift
// バッチ処理でネットワークアクセスをまとめる
class BatchNetworkManager {
    private var pendingRequests: [URLRequest] = []
    private var batchTimer: Timer?
    
    func enqueue(_ request: URLRequest) {
        pendingRequests.append(request)
        
        // 5秒後にバッチ実行
        batchTimer?.invalidate()
        batchTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { [weak self] _ in
            self?.executeBatch()
        }
    }
    
    private func executeBatch() {
        guard !pendingRequests.isEmpty else { return }
        
        // バッチリクエストを実行
        let requests = pendingRequests
        pendingRequests = []
        
        // 実行処理...
    }
}
```

### Phase 7: MetricKit によるモニタリング

```swift
import MetricKit

class MetricsManager: NSObject, MXMetricManagerSubscriber {
    
    static let shared = MetricsManager()
    
    private override init() {
        super.init()
        MXMetricManager.shared.add(self)
    }
    
    // iOS 13+: 日次レポート
    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            // 起動時間
            if let launchMetrics = payload.applicationLaunchMetrics {
                analyzeLaunchMetrics(launchMetrics)
            }
            
            // メモリ
            if let memoryMetrics = payload.memoryMetrics {
                analyzeMemoryMetrics(memoryMetrics)
            }
            
            // CPU
            if let cpuMetrics = payload.cpuMetrics {
                analyzeCPUMetrics(cpuMetrics)
            }
        }
    }
    
    // iOS 14+: 診断レポート
    func didReceive(_ payloads: [MXDiagnosticPayload]) {
        for payload in payloads {
            // クラッシュ
            if let crashDiagnostics = payload.crashDiagnostics {
                analyzeCrashes(crashDiagnostics)
            }
            
            // ハング
            if let hangDiagnostics = payload.hangDiagnostics {
                analyzeHangs(hangDiagnostics)
            }
        }
    }
    
    private func analyzeLaunchMetrics(_ metrics: MXAppLaunchMetric) {
        let resumeTime = metrics.histogrammedApplicationResumeTime
        let launchTime = metrics.histogrammedTimeToFirstDraw
        
        // 閾値を超えたらアラート
        // ...
    }
}
```

## ガードレール

### 必須遵守事項

1. **計測前後の比較**: 最適化前後で必ず計測し効果を検証
2. **複数デバイスでの検証**: 最低スペックのサポートデバイスでテスト
3. **リリース構成でのテスト**: Debug ではなく Release でプロファイリング
4. **継続的モニタリング**: MetricKit でリリース後も監視

### 禁止事項

1. **可読性の犠牲**: 極端な最適化で保守性を損なわない
2. **機能の削減**: パフォーマンスのためにUXを損なわない

### 警告事項

1. **シミュレータでの計測**: 実機と性能特性が異なる
2. **デバッグビルド**: オーバーヘッドが大きい
3. **サンプルサイズ**: 複数回計測して平均を取る
4. **環境要因**: バッテリー残量、温度、他アプリの影響

## 参照

- references/instruments-guide.md: Instruments活用ガイド
- references/memory-optimization.md: メモリ最適化詳細
- references/cpu-optimization.md: CPU最適化詳細
- references/rendering-optimization.md: レンダリング最適化詳細
- references/launch-optimization.md: 起動時間最適化詳細
- references/battery-optimization.md: バッテリー最適化詳細

