iOSアプリのパフォーマンス最適化支援スキル。Instruments活用、メモリ/CPU/GPU使用率分析、アプリ起動時間・バッテリー消費の最適化をサポート。Use when: パフォーマンス問題の調査、アプリ最適化、メモリリーク検出、起動時間短縮、バッテリー消費改善。
Analyzes iOS app performance issues using Instruments to profile memory, CPU, GPU, and battery usage. Provides optimization strategies for launch times, leaks, and rendering efficiency.
/plugin marketplace add CAPHTECH/claude-marketplace/plugin install apple-platform-plugin@caphtech-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/battery-optimization.mdreferences/cpu-optimization.mdreferences/instruments-guide.mdreferences/launch-optimization.mdreferences/memory-optimization.mdreferences/rendering-optimization.mdiOSアプリケーションのパフォーマンス最適化を包括的に支援するスキル。 Instruments等のツールを活用した計測から、具体的な改善施策の実装までをカバーする。
以下の状況でこのスキルを起動する:
| 指標 | 目標値 | 計測方法 |
|---|---|---|
| 起動時間(Cold) | < 400ms | Instruments / MetricKit |
| 起動時間(Warm) | < 200ms | Instruments / MetricKit |
| メモリ使用量 | < 100MB(基本機能) | Instruments / Xcode Memory Gauge |
| CPU使用率(アイドル) | < 1% | Instruments |
| フレームレート | 60fps / 120fps | Instruments / CADisplayLink |
| バッテリー消費 | 低〜中 | Instruments / MetricKit |
詳細は references/instruments-guide.md を参照。
| 問題 | 推奨 Instrument |
|---|---|
| メモリリーク | Leaks |
| メモリ使用量 | Allocations |
| CPU使用率 | Time Profiler |
| UI性能 | Core Animation / Animation Hitches |
| 起動時間 | App Launch |
| バッテリー | Energy Log |
| ネットワーク | Network |
詳細は references/memory-optimization.md を参照。
// 循環参照の典型例と修正
// Bad: 循環参照
class ViewController: UIViewController {
var completion: (() -> Void)?
func setup() {
completion = {
self.doSomething() // strong reference cycle
}
}
}
// Good: weak self で循環参照を回避
class ViewController: UIViewController {
var completion: (() -> Void)?
func setup() {
completion = { [weak self] in
self?.doSomething()
}
}
}
// 画像メモリ最適化
// 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)
}
詳細は references/cpu-optimization.md を参照。
// 重い処理の特定と最適化
// 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)
}
}
}
// アルゴリズムの改善
// Bad: O(n^2) の検索
func findDuplicates(in array: [Int]) -> [Int] {
var duplicates: [Int] = []
for i in 0..<array.count {
for j in (i+1)..<array.count {
if array[i] == array[j] && !duplicates.contains(array[i]) {
duplicates.append(array[i])
}
}
}
return duplicates
}
// Good: O(n) の検索
func findDuplicates(in array: [Int]) -> [Int] {
var seen = Set<Int>()
var duplicates = Set<Int>()
for element in array {
if seen.contains(element) {
duplicates.insert(element)
} else {
seen.insert(element)
}
}
return Array(duplicates)
}
詳細は references/rendering-optimization.md を参照。
// 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
}
}
// 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)
詳細は references/launch-optimization.md を参照。
Cold Launch:
1. dyld: ライブラリ読み込み
2. Runtime: クラス初期化、+load メソッド
3. UIKit: AppDelegate/SceneDelegate 初期化
4. Initial Frame: 最初の画面描画
Warm Launch:
- プロセスは生きているが、バックグラウンドから復帰
- UIの再構築が必要
// アプリ起動時間の計測
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
}
// 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!
}
}
詳細は references/battery-optimization.md を参照。
| 要因 | 影響度 | 対策 |
|---|---|---|
| 位置情報 | 高 | 精度を下げる、バックグラウンド更新を制限 |
| ネットワーク | 高 | バッチ処理、適切なタイミングで通信 |
| CPU | 中 | 処理の効率化、アイドル時の処理削減 |
| GPU | 中 | 不要なアニメーション削減 |
| センサー | 低〜中 | 必要時のみ使用 |
// Bad: 常に高精度
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
// Good: 用途に応じた精度
// ナビゲーション時
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
// 大まかな位置のみ必要な場合
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
// 重要な位置変化のみ
locationManager.startMonitoringSignificantLocationChanges()
// バッチ処理でネットワークアクセスをまとめる
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 = []
// 実行処理...
}
}
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
// 閾値を超えたらアラート
// ...
}
}
# パフォーマンス分析レポート
## 概要
- 分析日: YYYY-MM-DD
- 対象バージョン: x.x.x
- テストデバイス: iPhone 15 Pro (iOS 17.2)
## 計測結果
### 起動時間
| 種別 | 計測値 | 目標 | 判定 |
|-----|--------|-----|------|
| Cold Launch | 350ms | <400ms | OK |
| Warm Launch | 180ms | <200ms | OK |
### メモリ使用量
| 状態 | 使用量 | 目標 | 判定 |
|-----|--------|-----|------|
| 起動直後 | 45MB | <50MB | OK |
| リスト表示 | 80MB | <100MB | OK |
| ピーク | 150MB | <200MB | OK |
### CPU使用率
| 状態 | 使用率 | 目標 | 判定 |
|-----|--------|-----|------|
| アイドル | 0.5% | <1% | OK |
| スクロール中 | 25% | <30% | OK |
## 検出された問題
### 問題1: メモリリーク
- 場所: ProfileViewController
- 原因: クロージャでのstrong reference cycle
- 影響: 画面遷移ごとに2MB増加
- 推奨修正: [weak self] の追加
### 問題2: フレームドロップ
- 場所: ProductListView
- 原因: セル再利用時の画像デコード
- 影響: スクロール時に55fps以下
- 推奨修正: 画像のプリフェッチと適切なサイズへのダウンサンプリング
## 改善提案
1. **優先度: 高**
- メモリリークの修正
- 推定効果: メモリ使用量 20% 削減
2. **優先度: 中**
- 画像処理の最適化
- 推定効果: スクロール性能 30% 向上
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: バッテリー最適化詳細This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.