SwiftUI:为什么init()按相反顺序调用?

huangapple go评论96阅读模式
英文:

SwiftUI: Why are init() called in reversed order?

问题

  1. 为什么当环境对象没有更新时,ContentView2的init方法会被调用?
  2. 在我的理解中,我期望init()调用应该从ContentView到View1再到View2的层次,但实际上ContentView的init()会在后面被调用。为什么会这样?

点击"View1"按钮后,打印输出:

init View1
init ContentView

点击"View2"按钮后,打印输出:

init View1
init View2
init ContentView

以下是简短的示例代码:
ContentView2.swift

import SwiftUI

class Model: ObservableObject {
    @Published var strA: String = "strA"
    @Published var strB: String = "strB"
    @Published var strC: String = "strC"
}
class Model2: ObservableObject {
    @Published var showView1: Bool = false
    @Published var showView2: Bool = false
}

struct ContentView2: View {
    @EnvironmentObject var model2: Model2
    init() {
        print("init ContentView")
    }

    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)

            HStack {
                if model2.showView1 {
                    ViewA()
                }
                if model2.showView2 {
                    ViewB()
                }
            }
            .border(.black)
            HStack {
                Button("View1") { model2.showView1.toggle() }
                Button("View2") { model2.showView2.toggle() }
            }
        }
        .padding()
    }
}

ViewA.swift

struct ViewA: View {
    @EnvironmentObject var model: Model

    init() {
        print("init View1")
    }

    var body: some View {
        Button("Click") {
            print("Click view1 button")
            model.strC = "strC Changed"
        }
        Text("viewA text : \(model.strB)")
    }
}

ViewB.swift

struct ViewB: View {
    @EnvironmentObject var model: Model

    init() {
        print("init View2")
    }

    var body: some View {
        VStack {
            Button("Click") {
                print("Click view2 button")
            }
            Text("viewB text : \(model.strC)")
        }
    }
}
英文:

I'm using environmentObject to update a string.

  1. Why does does ContentView2's init called when no the environmentObject it's using was not updated?
  2. In my mind, i would expect the init() calls to follow the hierarchy from contentView to view1 than view2, but instead ContentView's init() is called later? why is that so?

upon clicking "View1" Button, print output:

init View1
init ContentView

upon clicking "View2" Button, print output:

init View1
init View2
init ContentView

Here's the short sample code:
ContentView2.swift

import SwiftUI

class Model: ObservableObject {
    @Published var strA: String = "strA"
    @Published var strB: String = "strB"
    @Published var strC: String = "strC"
}
class Model2: ObservableObject {
    @Published var showView1: Bool = false
    @Published var showView2: Bool = false
}

struct ContentView2: View {
    @EnvironmentObject var model2: Model2
    init() {
        print("init ContentView")
    }

    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)

            HStack {
                if model2.showView1 {
                    ViewA()
                }
                if model2.showView2 {
                    ViewB()
                }
            }
            .border(.black)
            HStack {
                Button("View1") { model2.showView1.toggle() }
                Button("View2") { model2.showView2.toggle() }
            }
        }
        .padding()
    }
}

ViewA.swift

struct ViewA: View {
    @EnvironmentObject var model: Model

    init() {
        print("init View1")
    }

    var body: some View {
        Button("Click") {
            print("Click view1 button")
            model.strC = "strC Changed"
        }
        Text("viewA text : \(model.strB)")
    }
}

ViewB.swift

struct ViewB: View {
    @EnvironmentObject var model: Model

    init() {
        print("init View2")
    }

    var body: some View {
        VStack {
            Button("Click") {
                print("Click view2 button")
            }
            Text("viewB text : \(model.strC)")
        }
    }
}

答案1

得分: 1

我猜你把@StateObject放在你的App结构体中,而不是使用单例来管理你的存储对象,例如.environmentObject(Model.shared)。当你使用这些对象包装器时,body会被调用,无论是否访问了属性。这就是为什么你应该尽量在你可以的地方使用值类型,例如使用@State,只有当状态发生变化时,body才会被调用,如果之前在body中读取过的话。

不用担心,SwiftUI对于这些View数据描述使用结构体意味着初始化它们基本上可以忽略不计。这就像初始化一个整数一样,你永远不会关心它。SwiftUI会对它们进行差异比较,只有在与上次不同的情况下才会执行任何操作。因此,这些View结构体不断被初始化和丢弃,这就是为什么你不应该在body内部执行任何重操作,比如在body内部初始化一个对象。

英文:

My guess is you have put @StateObject in your App struct instead of using a singleton for your store object, e.g. .environmentObject(Model.shared). When you use these object wrappers, body is called regardless if a property is accessed or not. That's why you should try to use value types everywhere you can, e.g. with @State, body is only called when the state changes if it was previously read in body.

Don't worry, SwiftUI's use of structs for these View data descriptions means it is basically negligible to init them. Its like initing an int, you would never care about that. SwiftUI diffs them and it actually only does anything when something is different from last time. So these View structs are constantly being init and discarded, that's one reason why you shouldn't do anything heavy like init an object inside body.

答案2

得分: 1

补充@malhal的答案:View 调用 init 并不一定意味着底层视图内容正在重新加载。这些 View 本身是底层实际显示在屏幕上的视觉对象的抽象。

SwiftUI 可能会因为不明显的原因重新创建视图,然后根据它们的显式结构身份进行内部比较(详见 WWDC21 的演讲 #10022 了解更多信息)。如果这些内容发生了变化,它将重新绘制视图(或视图的部分),如果没有变化,它将尽量避免重新绘制。

不要根据视图创建或丢弃的时间来推断视图是否正在重新绘制。您应该使用提供的API(例如,在视图上使用 .id())并让 SwiftUI 决定何时需要更新。如果遇到性能问题,请使用 Instruments 的 SwiftUI 工具来查看哪些视图被过多地重新绘制,并从那里进行优化。

英文:

To add to @malhal's answer: a View calling init doesn't necessarily imply the underlying view content is being reloaded.
The Views themself are an abstraction of the underlying visual objects that are actually being displayed on the screen.

SwiftUI might recreate a view for a non-obvious reason and then internally compare the elements of the view based on their explicit or structural identities (see WWDC21 talk #10022 for more info).
It will redraw a view (or parts of the view) if these have changed, and will try not to if there's no change.

Don't try to deduce anything about view redrawing based on when your views are created or discarded.
You should use the APIs provided (e.g. .id() on a view) and leave SwiftUI to decide what needs to update and when.
If you're running into performance issues use Instruments' SwiftUI tool to see which views are being redrawn too much and optimize from there.

huangapple
  • 本文由 发表于 2023年5月28日 17:08:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76350735.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定