SwiftUIは観測している状態が変わるたびに body を再実行し、新しいビューツリーを古いものと差分(diff)して、異なる部分だけを更新します。パフォーマンスの問題は、その差分に過剰な作業をさせることから生じます。不安定なアイデンティティ、広すぎる観測、そして高コストな body 計算です。アイデンティティを制御し、状態のスコープを絞り、body を安価に保つことで修正します。
SwiftUIは再描画をまたいでビューをアイデンティティで対応づけます。リストではこれが id です。idが不安定だと、SwiftUIはビューを更新する代わりに破棄して再構築します(状態やアニメーションを失います)。
// BAD: array index as id — reorders/inserts break identity, full rebuild
ForEach(items.indices, id: \.self) { i in Row(items[i]) }
// GOOD: stable identity tied to the data
ForEach(items) { item in Row(item) } // items: Identifiable, stable id
ObservableObject は、いずれかの @Published プロパティが変わると、それを観測しているすべてのビューを再描画します。大きなモデルを分割するか、Observation framework(@Observable、iOS 17+)に移行してください。これはプロパティごとの読み取りを追跡するため、ビューは実際に使ったプロパティが変わったときにだけ再描画されます。
@Observable final class Feed { // iOS 17: fine-grained dependency tracking
var unreadCount = 0 // a view reading only .posts won't re-render on this
var posts: [Post] = []
}
body は頻繁に実行されるため、重い処理をインラインで行ってはいけません:
var body: some View {
List(sortedItems) { ... } // BAD if sortedItems sorts on every render
}
// GOOD: compute derived data in the model, not in body
LazyVStack/List を使い、画面外の行が構築されないようにします。.equatable() や EquatableView を追加して、安定したサブツリーの差分検出を短絡させます。AnyView は型のアイデンティティを消去し、構造的な差分検出を妨げます。ホットパスでは避けてください。120HzのProMotionディスプレイでは1フレームあたり約8msしかありません。大きな配列をソートしたりリスト全体を再描画したりする body はフレームを落とし、スクロールをカクつかせます。シニア面接がビューのアイデンティティと観測スコープを探るのは、そこが実際のSwiftUIアプリが遅くなる場所であり、いくつかの的確な修正でなめらかさが戻る場所だからです。
ジュニアからシニアまで、詳細な回答付きのIT面接質問ライブラリ。
寄付する