SwiftUI: Lazy Stack

SwiftUI 有幾個 Stack 元件前加上了 Lazy,例如 LazyVStack、LazyHStack、LazyVGrid…等。加上 Lazy 的排版元件可以在需要載入內容並且選染畫面的時候才開始處理,而沒有 Lazy 的元件則是將所有需要載入與選染內容的工作在一開始就全部完成。當要顯示的資料筆數過多的時候,使用 Lazy 元件會減少記憶體使用與大幅提昇效能。

Lazy 元件需搭配 ScrollView 元件一起使用,以 LazyVStack 為例,在 ContentView 中加上下面這個結構,用來觀察到底在畫面出現時事先生成了多少個元件。

struct MyView: View {
    let i: Int
    init(_ i: Int) {
        self.i = i
        print(i)
    }
    var body: some View {
        Text(i, format: .number)
            .padding()
    }
}

ScrollView

然後執行下面這段程式碼。這時可以觀察到在 Xcode 右下方的 Preview 面板中大約只印出 10~15 左右的數字,表示此時只產生了 10 幾個 MyView 元件(這個數字每個人都不一樣,反正會比一個畫面能顯示的數量再多一點)。之後當使用者捲動捲軸時,才會依需要再產生。

// ✅

ScrollView {
    LazyVStack {
        ForEach(0..<200) { i in
            MyView(i)
        }
    }
}

如果將 LazyVStack 改成 VStack 結果就不一樣了,會一開始就印出 199,代表一次就產生了 200 個 MyView 元件,雖然實際上一個畫面不可能顯示這麼多資料,但此時還是要載入全部資料進記憶體並且開始進行排版,系統效能會被嚴重拖慢。

// ❌

ScrollView {
    VStack {
        ForEach(0..<200) { i in
            MyView(i)
        }
    }
}

List

List 元件需要配合 LazyVStack 嗎?答案是不用。List 元件本身就內建記憶體管理,只有在需要的時候才載入元件與進行排版,例如下面這段程式碼。預覽或執行時會在 Xcode 右下角的 Preview 面板中看到只產生 10 幾個 MyView 元件,捲動捲軸時才會依需要產生其餘的 MyView。然後千萬別在 ForEach 外面加上 LazyVStack,加上後在一開始就會建立所有 200 個 MyView 元件,效能反而變差了。

// ✅

List {
//    LazyVStack {
        ForEach(1..<200) { i in
            VStack {
                MyView(i)
            }
        }
//    }
}

如果只有 List 而沒有 ForEach,例如下面這段程式碼,此時使用 VStack 或 LazyVStack 都可以。

// ✅

List(1..<200) { i in
    VStack {
        MyView(i)
    }
}

但如果沒有 VStack 或 LazyVStack 時,就會一次產生 200 個元件。

// ❌

List(1..<200) { i in
    MyView(i)
}

■ Xcode 測試版本:15.4 與 16.0 beta 5

發表迴響