「なんとなく重い」「スクロールがカクつく」——Swiftアプリのパフォーマンス問題に悩んだことがあるなら、この記事はあなたのためにある。try! Swift Tokyo 2026でPaul Hudson(Hacking with Swiftの作者)が登壇したワークショップ「High-Performance Swift」の内容を、参加者のレポートをもとに徹底解説する。
パフォーマンス計測の「5つのルール」
Paulが最初に強調したのは、Instrumentsを使う習慣そのものだ。ツールの使い方より先に、計測のマインドセットが重要だという。
- Run it often(頻繁に実行する)
- Run it before changes(変更前に実行する)
- Run it after changes(変更後に実行する)
- Run it on device(実機で実行する)
- Run it alongside other tools(他のツールや直感と組み合わせる)
特に「実機」にこだわる点が印象的だ。最新のiPhone ProやM4 MacBook Airでは問題が見えなくても、ミドルレンジのiPadでは如実に出る。「一般ユーザーに近い端末でプロファイリングせよ」というのは、開発者が陥りがちな罠を突いた指摘だ。
ARCとStringの「罠」——Swiftが遅くなる本当の理由
SwiftのARC(Automatic Reference Counting)はスレッドセーフにするためアトミック操作でretain/releaseを行う。この「ロック」が積み重なると、見えないオーバーヘッドになる。
特に注意したいのが StringとArrayの内部実装だ。これらは値型として振る舞うが、内部は参照型クラス(__StringStorageなど)で実装されている。コピーのたびにretainが走るため、ループ内で大量にコピーされると負荷になる。
さらに、structに参照型プロパティが複数ある場合も要注意。値渡しのたびにプロパティの数だけretain/releaseが走る。こういったケースではclassに変換することで、インスタンス1回分のARC呼び出しに抑えられる。
reserveCapacityの1行で配列パフォーマンスが変わる
Arrayは初期容量ゼロから始まり、容量超過のたびに倍々で再アロケーションが走る(malloc→全要素コピー→旧バッファfree)。これをゼロコストで避けられるのがreserveCapacityだ。
// Before: 再アロケーションが何度も発生
var results: [Website] = []
for source in sources {
results.append(process(source))
}
// After: 1行追加するだけで再アロケーションを完全回避
var results: [Website] = []
results.reserveCapacity(sources.count) // ← これだけ
for source in sources {
results.append(process(source))
}
InstrumentsのAllocationsで確認すると、Before版では_ContiguousArrayStorageが何度も生成・破棄されているのが見える。After版では最終サイズのインスタンスが1つだけになる。なおmapは内部でreserveCapacityを使っているため、for+appendよりmapを使う方が自然にこの恩恵を受けられる。
SwiftUIのスクロール遅延・画面遷移遅延の診断と修正
Instrumentsには3つの主要ツールがある。
- Time Profiler: どのコードが遅いかを「Heaviest Stack Trace」で特定
- Allocations: ヒープ上のアロケーションを追跡、配列の再アロケーションも可視化
- SwiftUI Instrument:
bodyの再評価回数と「Cause and Effect Graph」で再レンダリングの原因を特定
スクロール遅延のよくある原因は、computed propertyのfilteredEmployeesがbody評価のたびに再計算されるパターン。SwiftUI Instrumentで「スクロール→@State更新→ContentView.body再評価」のトリガーを特定し、computed→stored propertyに変換するだけで解消できる。
画面遷移遅延は、重い同期処理がメインスレッドをブロックしているケースが多い。修正方針は「処理クラスをactorに変換 → メソッドをasyncに → ビューからTask + awaitで呼ぶ」の3ステップだ。
まとめ:「計測→特定→修正→再計測」のループを体に染み込ませる
このワークショップの本質は、特定のテクニックよりも「Instruments→特定→コード修正→再計測」というサイクルを反射的に実行できるようになることだと感じる。reserveCapacityの1行、memoizationのキャッシュ退避、actor化によるバックグラウンド処理——どれも「問題を可視化してから初めて意味を持つ」手法だ。
SwiftのOSSコード(stdlib/public/core/StringStorage.swiftなど)を読んで内部実装を確認する演習もあり、「なぜそうなるか」を標準ライブラリのコードで検証できる点がPaul流の徹底したアプローチだ。元記事では各手法の演習課題も詳細にまとめられているので、ハンズオンで試したい方はぜひ参照してほしい。