SwiftUI @State @Published @ObservedObject Deep understanding and use

SwiftUI @State @Published @ObservedObject Deep understanding and use

SwiftUI is Apple's new future-oriented, cross-terminal solution, declarative programming

最后更新 10/18/2021 4:51 PM
Renew全栈工程师
预计阅读 7 分钟
分类
Swift UI
标签
ObservedObject Published State
  1. SwiftUI is Apple's new future-oriented, cross-terminal solution, declarative programming

SwiftUI's latest version 2.0 requires IOS 14 support. Most of them still use IOS 13, so many imperfect things are replaced with SwiftUIX and various libraries, and bugs are also emerging endlessly.

  1. Below is my understanding of @State @Published @ObservedObject. If anything is wrong, please point out

1.@ State Introduction

Because SwiftUI View uses a structure, when creating a structure method that we want to change properties, we need to add the mutating keyword, for example:

mutating func doSomeWork()

然而,Swift 不允许我们创建可变计算属性,这意味着我们不能编写mutating var body: some View——这是不允许的。

@State allows us to bypass the limitations of structs: we know we cannot change their properties because the structs are fixed, but @State allows SwiftUI to store the value separately where it can be modified.

Yes, it feels a bit like cheating, and you may be wondering why we don't use classes-they can be freely modified. But believe me, it's worth it: As you progress, you'll learn that SwiftUIs often destroy and recreate your structures, so keeping them small and simple is important for performance.

Tip: There are several ways to store program state in SwiftUI, and you will learn all of them.@ State is specifically designed for simple properties stored in a view. Therefore, Apple recommends that we add private access controls to these attributes, such as @State private var tapCount = 0.

2.@ Published + @ ObserverdObject Introduction

@Published is one of SwiftUI's most useful wrappers, allowing us to create object properties that can be automatically observed. SwiftUI will automatically monitor this property and automatically modify the interface bound to the property once it changes.

For example, the data structure Model we define is provided that @Published is used under ObservableObject Then use @ObservedObject to refer to this object. Of course,@State will not report an error, but it cannot be updated.

class BaseModel: ObservableObject{
    @Published var name:String = ""
}
struct ContentView: View{
  @ObservedObject var baseModel:BaseModel = BaseModel()
  var body: some View{
      Text("用户名\(baseModel.name)")
      Button(action: {
              baseModel.name  = "Renew"
            }, label: {
                Text("更新视图")
            })
  }
}

3. The most important part (the code comments part is the most important, be sure to read it all)

Although everything is displayed and loaded normally in the above case, in the actual project, there are a bunch of bugs. How did this cause? If you don't understand the relationship between these three states and View binding, you may leave hidden dangers for yourself.

** Let's look at the group case first **

//// MASK - 先定义两个Model 继承 ObservableObject
class WorkModel: ObservableObject {

    @Published var name = "name"

    @Published var count = 1
}
class UserModel: ObservableObject {

    @Published var nickname = "nickname"

    @Published var header = "http://www.baidu.com"
}
//// MASK - View显示层
struct ContentView: View {
    @ObservedObject var workModel:WorkModel = WorkModel()

    @ObservedObject var userModel:UserModel = UserModel()
    var body: some View {
        VStack{
            Text("work.count \(workModel.count)")
            Text("work.name \(workModel.name)")
            Text("user.nickname \(userModel.nickname)")
            Text("user.header \(userModel.header)")

            Button(action: {
               userModel.nickname = "Renew"
               userModel.header = "http://..."
               workModel.name = "work name"

               workModel.count += 1
            }, label: {
                Text("更新数据")
            })
        }
     }
}

Not surprisingly, the above code updates the data by clicking the button, but what if we had a packaging class

class WrapperModel: ObservableObject{

    @ObservedObject var workModel:WorkModel = WorkModel()

    @ObservedObject var userModel:UserModel = UserModel()
}
struct ContentView: View {
    @ObservedObject var wrapperModel:WrapperModel = WrapperModel()
    var body: some View {
        VStack{
            Text("work.count \(wrapperModel.workModel.count)")
            Text("work.name \(wrapperModel.workModel.name)")
            Text("user.nickname \(wrapperModel.userModel.nickname)")
            Text("work.header \(wrapperModel.userModel.header)")

            Button(action: {
               wrapperModel.userModel.nickname = "Renew"
               wrapperModel.userModel.header = "http://..."
               wrapperModel.workModel.name = "work name"

               wrapperModel.workModel.count += 1
            }, label: {
                Text("更新数据")
            })
        }
     }
}

Will the data be updated by clicking the button at this time? The answer is no. So why is this???

因为SwiftUI更新数据的前提是触发
第一层 绑定的对象 wrapperModel下的属性(字段)发生更新才会调用视图层更新数据
但是 第一次下绑定的对象还绑定了 @ObservedObject 或者其他类型的对象呢?
还会触发第一次对象属性更新吗,答案是不能的
你可以在 didSet 事件里面捕捉,是捕捉不到的,所以视图是不会更新的,那这还有其他解决方案吗

There are:

Call the object wrapperModel.objectWillChange.send() method to tell the View layer that I update But is this absolute?: It's not that if a deeper model still has bugs and cannot be triggered

4. Summary and solutions

/// 既然我们知道View 跟 状态绑定的关系
/// 是以第一继承ObservableObject 类 下的属性(字段)更新来更新视图的
/// 那我们可以给 ObservableObject 加一个 无关紧要的字段,然后编写一个方法,来通知更新
class BaseobservableObject: ObservableObject {
    ///
    /// 注意
    /// 接收 子类model 时候要用   @ObservedObject 不能用 @Published
    /// 因为SwiftUI 更新机制是当前对象有 @Published 字段更新 就会调用View视图进行更新
    /// 在BaseModel里面实现 notifyUpdate 更新当前对象 _lastUpdateTime 字段,实现自身全部字段更新
    @Published private var _lastUpdateTime: Date = Date()
    ///
    /// 通知更新
    public func notifyUpdate() {
        _lastUpdateTime = Date()
    }
}
/// 那当我们 包装类下的对象更新的时候
/// 可以直接 调用包装类 notifyUpdate() 方法更新当前对象属性,来达到更新View 的效果
/// 顾忌:如果多次调用 notifyUpdate() View会刷新两边吗
/// 答案是否定的,再一次函数栈里面 多次调用 notifyUpdate() View也只更新一次
/// 当子类继承了 BaseobservableObject 对象
/// 那么该对象下面属性其实可以不需要在写 @ObservedObject 或者 @Published 了
/// 因为更新属性之后调用了 notifyUpdate() 达到了更新整个对象的效果,所以可以省略了

5. other intellectual

/// MASK - 实现一个基础Model类,其他Model继承该类
class BaseModel: ObservableObject {

   @Published var isLoading = false
}
class SonModel: BaseModel {

    @Published var name = "name"

    @Published var count = 1
}
struct ContentView: View {
    @ObservedObject var sonModel:SonModel = SonModel()
    var body: some View {
        VStack{
            Text("name \(sonModel.name)")
            Button(action: {
               sonModel.name = "Renew"
            }, label: {
                Text("加载")
            })
        }
     }
}
/// 问题来了,现在我View 层我直接引用
/// 照说这时候应该 Text 提示信息是 name Renew 但是点击没反应
/// 啥原因,问题其实还是跟上面的问题有点相似
/// SonModel 不是直接继承于 ObservableObject 类的
/// 所以,直接继承 ObservableObject 下的属性(字段)没更新,就不会更新View
/// 最简单的解决办法就是 更新直接继承 ObservableObject(父对象) 里面的随便一个属性
Keep Exploring

延伸阅读

更多文章