Open utterances-bot opened 3 years ago
如果不希望 model 状态在 View 刷新时丢失,那确实可以进行替换,这 (虽然可能会对性能有一些影响,但) 不会影响整体的行为。
有点奇怪,已经说了 model 不会重复,为什么会说可能会对性能有影响。
这个 session 26m 的时候也提到了,在 view 频繁创建的场景提倡使用 @StateObject。
个人的一点理解来看,@StateObject 生命周期与真实视图(UIKit 中)一致,而 @ObservedObject 生命周期与虚拟视图(SwiftUI 中)一致。
喵大,为什么上面的例子中当 niceScore 是4的时候,Nice? 还是显示 NO,您的代码中写的是 if model.score > 3 的时候 niceScore 已经为 ture 了呀
喵大,为什么上面的例子中当 niceScore 是4的时候,Nice? 还是显示 NO,您的代码中写的是 if model.score > 3 的时候 niceScore 已经为 ture 了呀
如果你仔细看下源码:
Button("+1") {
if model.score > 3 {
niceScore = true
}
model.score += 1
}
可以看到 score 是后加的。因此你说的场景,当 score 为 3 时,点击 button,此时 model.score > 3 的判断并不会进入。
PS: 由于有邮件提醒,因此我回复你一下。 @dongdongya
好的,谢谢喵大,抱歉是我没有认真思考
@State或者@StateObject是如何来确保在View的生命周期期间只创建一次的?
倘若想:在 @StateObject
修饰的属性初始化时,进行某些数据的注入。
但由于 @StateObject
修饰的属性变成了只读,一般情况下只能使用默认的无参数初始化。
在网上找了一圈,发现都是建议在 View 的 onAppear
时再进行数据的“真正初始化”。
不知道上述的初始化注入,在 SwiftUI 中是否属于反模式呢?又或者有其他我还没有领悟到的做法?
搭个楼请教一下喵大和各位,谢谢。
@Monsoir 这个其实在另外一篇文章里也有一点涉及
https://onevcat.com/2021/01/swiftui-state/
我个人的认知上来说,可能可以总结几点:
@State
或者 @StateObject
的方式,是反模式的。虽然通过一些技巧可以实现 (比如 onAppear
时的赋值),但是这种时候你需要确保你真的理解 View
的 onAppear
或者 init 的生命周期,而且“长期”关注它在今后可能的变化,因为这可能会破坏最初的假设。@State
标记为 private”,但实际上其实 @StateObject
应该也需要是 private。@Binding
和 @ObservedObject
会更正确。如果一定需要进行所谓的“注入”,暂时我也没发现比 onAppear
更好的方法。不过可能注意一下,不要把整个 @StateObject
做注入,而只是保持 private
和声明 @StateObject
时直接赋值,然后在 init 中只传递需要初始化的那些数据,并在 onAppear
时设置这些数据 (甚至可以判断一下是不是已经初始化过了,因为这个时候就算是意外调用,@StateObject
也还是原来的那个),会是更安全的做法。
@Mamong
虽然不太准确,但是你可以理解为内部维护了一个字典,来维持“显示的 View” -> “数据存储“的映射。这个的话今年 WWDC 的 SwiftUI 解密的 session 总结了不少。https://developer.apple.com/videos/play/wwdc2021/10022/
如果在ScorePlate的View中添加一个Button("xxx"){showRealName2.toggle()}, @ObservedObject var model = Model() 然后进入这个页面的时候点击”xxx"按钮,为什么没有没有把score的值清零呢? 代码
struct ScorePlate: View {
@State private var showRealName2 = false
@ObservedObject var model = Model()
@State private var niceScore = false
var body: some View {
VStack {
Button("+1") {
if model.score > 3 {
niceScore = true
}
model.score += 1
}
Text("Score: \(model.score)")
Text("Nice? \(niceScore ? "YES" : "NO")")
Button("Toggle Name") {
showRealName2.toggle()
}
ScoreText(model: model).padding(.top, 20)
}
}
}
点击xxx按钮没有引起这个页面的View刷新吗?
@onevcat
struct ContentView: View {
@State private var showRealName = false
var body: some View {
NavigationView{
VStack {
Button("Toggle Name") {
showRealName.toggle()
}
Text("Current User: \(showRealName ? "Wei Wang" : "onevcat")")
//ScorePlate().padding(.top, 20)
NavigationLink("Next", destination: ScorePlate().padding(.top, 20))
}
}
}
}
class Model: ObservableObject {
init() { print("Model Created") }
@Published var score: Int = 0
}
struct ScorePlate: View {
@State private var showRealName2 = false
@ObservedObject var model = Model()
@State private var niceScore = false
var body: some View {
VStack {
Button("+1") {
if model.score > 3 {
niceScore = true
}
model.score += 1
}
Text("Score: \(model.score)")
Text("Nice? \(niceScore ? "YES" : "NO")")
Button("Toggle Name") {
showRealName2.toggle()
}
ScoreText(model: model).padding(.top, 20)
}
}
}
struct ScoreText: View {
@ObservedObject var model: Model
var body: some View {
if model.score > 10 {
return Text("Fantastic")
} else if model.score > 3 {
return Text("Good")
} else {
return Text("Ummmm...")
}
}
}
@k7arm showRealName2
这个 state 的变化没有导致 body 的变化,所以 UI 不会更新。
如果 View 本身就期望每次刷新时获得一个全新的状态,那么对于那些不是自己创建的,而是从外界接受的 ObservableObject 来说,@StateObject 反而是不合适的。
我试了下,貌似如果 改成外部传入 model
ScorePlate(model: model).padding(.top, 20)
不管里面ScorePlate是用 @StateObject var model:Model
还是 @ObservedObject var model:Model
,效果都一样。所以如果是获取一个全新的状态是不是不能采用这种外面传入model的形式?
另外请问下假如外面使用@StateObject
创建的model,传到子view里面又用@StateObject
声明了一次,这两次会导致创建两块存储么?还是两次@StateObject
实际上指向的同一个内存?
使用改为 @StateObject
之前的代码的确能看到 Model 在不断 init,但奇怪的是没有 deinit。似乎这些 Model 都没有被释放,不知道这是什么原因
@StateObject 和 @ObservedObject 的区别和使用 | OneV's Den
WWDC 2020 中,SwiftUI 迎来了非常多的变更。相比于 2019 年的初版,可以说 SwiftUI 达到了一个相对可用的状态。从这篇文章开始,我打算写几篇文章来介绍一些重要的变化和新追加的内容。如果你需要 SwiftUI 的入门和基本概念的材料,我参与的两本书籍《SwiftUI 与 Combine 编程》和
https://onevcat.com/2020/06/stateobject/