问题
TL;DR
It seems that the ContentView
below evaluates the body's if
statement before init
has run. Is there a race condition, or is my mental model out-of-order?
Kudos
A shout-out to Asperi, who provided the state-initializer equivalent that solves today's problem.
Code
Why does ContentView
display "dummy is nil"? It seems something gets closed over before the initializer sets dummy
. What is it about the second assignment that fixes things?
class Dummy { }
struct ContentView: View {
@State private var dummy : Dummy?
init() {
print("Init") // In either case, is printed before "Body"
// Using this assignment, "dummy is nil" shows on screen.
self.dummy = Dummy()
// Using this, "dummy is non-nil" shows on screen.
// From https://stackoverflow.com/questions/61650040/swiftui-initializer-apparent-circularity
// self._dummy = State(initialValue: Dummy())
}
var body: some View {
print("Body")
return ZStack {
if dummy == nil { // Decision seems to be taken
Text("dummy is nil" ) // before init() has finished.
} else {
Text("dummy is non-nil")
}
}
}
}
回答1:
Consensus is that it's a feature that looks like a bug.
Helpful discussion in Swift Forum. Also here. Highlights (edited for clarity) include:
Good reasons not to do this in the first place:
You're not suppose to mutate during
View
init since that'd be in thebody
call of the parent view@State
variables in SwiftUI should not be initialized from data you pass down through the initializer; since the model is maintained outside of the view, there is no guarantee that the value will really be used.Don't try overriding
@State
's initial value duringinit
. It will only work once during very first view creation (important: not value initialization) and only if that view in the view tree gets re-created / replaced with a different view of the same type but internally different id.
The "it's a misfeature" position (close to mine):
- This is due to an artificial limitation in the compiler's implementation of property wrappers. The workaround is to initialize the backing storage directly via
_value
Explanations of how and why:
The value in
@State
will always be initialized with the value you pass ininit
, this is simple Swift. However before nextbody
call SwiftUI will call update method and reinject a value into it if there was a previous value which will override your value from init.This is NOT a bug! :man_facepalming: It works as expected/advertised. ...
@State
property wrapper adds one possible way of mutation to the view, which also is meant to be private to the view, not initialized from parent, etc.
It's pilot error (probably true):
- There is NO problem here except people misunderstanding what State is build for.
来源:https://stackoverflow.com/questions/61661581/swiftui-view-apparently-laid-out-before-init-runs