じゃあ全部LazyVStackにすればええやん!
あらすじ
LazyVStackってなんやろ?VStackと何が違うんや?
↓
サイト「LazyはViewが画面に表示されるまでレンダリングされないため、パフォーマンスが素晴らしいです!」
↓
じゃあ全部LazyVStackにすればええやん!!!!
と私は考えてしまったため、なぜ安易に通常のStack(VStack/HStack)の代わりにLazy Stackを使うべきではないのかを、この記事では書いていきます。
Lazy Stackにすると不要なオーバーヘッドが発生する
Apple Developer Forums - What are the downsides to using lazy stacks?
公式がLazy Stackのデメリットについて提示してくれています。
Lazy stacks incur a small amount of extra overhead, both in time and memory, to handle the bookkeeping for what views have and have not been instantiated. In cases where all the views have to be instantiated anyway, that overhead is pure cost with no benefit.
訳: Lazy Stackでは、インスタンス化されたビューとインスタンス化されていないViewの帳簿を処理するために、時間とメモリの両方で、わずかな余分なオーバーヘッドが発生します。 すべてのViewをインスタンス化しなければならない場合、このオーバーヘッドは純粋なコストであり、何のメリットもありません。
実際に試してみましょう。
ContentView.swiftstruct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 10) {
ForEach(0..<100, id: \.self) { index in
ZStack {
Color.blue
Text("\(index)")
.font(.title)
.foregroundStyle(.white)
}
.frame(height: 30)
.onAppear {
print(index)
}
}
}
}
}
}
LazyVStackで囲まれた、数字が100個並んだViewを作ってみました。
描画時(インスタンス化された時)にそれぞれのインデックスをプリントするようにしています。
当たり前なのですが、画面を下にスクロールすると↓のようにプリントされます。
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
単純なViewなので特にカクついたりしませんが、複雑なViewを表示する際はパフォーマンスに影響が出そうです。
画面に表示されなくなったViewが再表示される際に、またインスタンス化の処理が走ります。
試しに上にスクロールしてみると、、、
13
12
10
11
7
8
9
5
6
3
4
2
0
1
このような感じで、インスタンス化されたビューとインスタンス化されていないViewの管理を、SwiftUIが頑張らないといけないため、不要なLazy Stackはやめましょう。
先述したフォーラムの続きに↓のような記載がありました。
If lazy stacks were always the right answer, SwiftUI could have just made all stacks lazy.
訳: もし全部がLazy Stackでいいなら、SwiftUIは通常のStackなんて作らずに全部そうしてる
Lazy Stackでは孫Viewの状態がリセットされる
Medium - Tips and Considerations for Using Lazy Containers in SwiftUI
↑によると、ForEachでループ表示されているViewの、孫Viewが持つ状態が、再描画時にリセットされるそうです。
実際にコードを動かしてみます。(記事から少し変えています。)
ContentView.swiftstruct ContentView: View {
var body: some View {
List {
ForEach(0 ..< 100) { i in
ChildView(i: i)
}
}
}
}
struct ChildView: View {
@State var childState = false
let i: Int
var body: some View {
VStack {
Text("\(i)")
Toggle("子State", isOn: $childState)
GrandChildView(i: i)
}
}
}
struct GrandChildView: View {
@State var grandChildState = false
let i: Int
var body: some View {
VStack {
Toggle("孫State", isOn: $grandChildState)
}
.onAppear {
print(i, grandChildState)
}
}
}
孫の描画時にインデックスと、孫が持っている状態をプリントするようにしています。
0: 子と孫両方true
1: 子だけtrue
2: 孫だけtrue
にした後、下にスクロールして、再度上にスクロールして表示してみると。。
...
5 false
4 false
3 false
2 false
1 false
0 false
6 false
記事の内容通り、孫の状態がリセットされています。
Appleのエンジニアによるとこちらはバグではなく、レンダリングの効率とリソースの消費のバランスをとるためにこのような設計となっているようです。(記事参照)
🙂↕️最後まで読んでいただきありがとうございます🙂↕️