Appearance
前言
近期忙于重构代码,尝试落实MVVM思想,但可能理解有偏差,在为View整合ViewModel时出现严重卡顿问题,遂研究下ObservedObject和StateObject
问题代码:
@ObservedObject private var vm = HomeViewModel()
class HomeViewModel: ObservableObject {
@Published var isShowTab: Bool = false
@Published var listMode: ListMode = .project
@Published var isShowFloatBtn: Bool = false
//...
}
@ObservedObject private var vm = HomeViewModel()
class HomeViewModel: ObservableObject {
@Published var isShowTab: Bool = false
@Published var listMode: ListMode = .project
@Published var isShowFloatBtn: Bool = false
//...
}
在子view中复用了此vm
本以为将控制视图状态的变量存在vm里会合理些,也会使view层更简洁,但运行后发现当相关变量发生改变时导致了肉眼可见的卡顿。
ObservableObject
ObservableObject是Combine框架下的协议类型,针对引用类型设计,用于在多个View之间共享数据,目前app中的Store层就是用该协议实现。
为属性添加@Published可以实现监听的效果,当值发生改变时会发送信号通知相关View进行刷新,
一般这个操作会自动进行,但如果我们想控制发出信号的时机,比如保存数据库,条件过滤等操作后再通知更新UI,就需要用到objectWillChange,在ObservableObject协议扩展中实现,ObservableObjectPublisher类型,循Publisher协议。
实现:
jsx
class UserAuthentication: ObservableObject {
var username = "" {
willSet {
//条件过滤
//.......
objectWillChange.send()
}
}
}
class UserAuthentication: ObservableObject {
var username = "" {
willSet {
//条件过滤
//.......
objectWillChange.send()
}
}
}
@Published作用就是用来替代手动调用,当数据被set时自动发出通知
@ObservedObject
- 作为View的数据依赖,不被View持有,View更新时ObservedObject对象可能会被销毁
- 适合数据在SwiftUI外部存储,把@ObservedObject包裹的数据作为视图的依赖,比如数据库中存储的数据
目前app中的主数据包括和就是通过这种方式实现,直接被view层调用。
- 当view被销毁时,ObservedObject对象也会被销毁
@StateObject
- 针对引用类型设计,当View更新时,实例不会被销毁,与State类似,使得View本身拥有数据
- @StateObject 和 @ObservedObject 的区别就是实例是否被创建其的View所持有,其生命周期是否完全可控。
- StateObject行为类似ObservedObject对象,区别是StateObject由SwiftUI负责针对一个指定的View,创建和管理一个实例对象,不管多少次View更新,都能够使用本地对象数据而不丢失
对比
jsx
struct Test2: View {
@State var count = 0
var body: some View {
NavigationView{
List{
NavigationLink("@StateObject", destination: CountViewState())
NavigationLink("@ObservedObject", destination: CountViewObserved())
}
}
}
}
struct Test2: View {
@State var count = 0
var body: some View {
NavigationView{
List{
NavigationLink("@StateObject", destination: CountViewState())
NavigationLink("@ObservedObject", destination: CountViewObserved())
}
}
}
}
测试2中,点击link进入对应的视图后通过点击+1进行计数,然后返回父视图。当再次进入link后,@StateObject对应的视图中计数清零,这是由于返回父视图,再次进入时会重新创建视图,所以会重新创建实例,不过@ObservedObject对应的视图中计数是不清零的。在这个测试中,@ObservedObject创建的实例生命周期长于当前的View。
测试3中点击按钮,在sheet中点击+1,当再次进入sheet后,无论是@StateObject还是@ObservedObject对应的View中的计数都被清零。