英文:
SwiftUI: Why are init() called in reversed order?
问题
- 为什么当环境对象没有更新时,ContentView2的init方法会被调用?
- 在我的理解中,我期望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.
- Why does does ContentView2's init called when no the environmentObject it's using was not updated?
- 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 View
s 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论