Skip to content
数据流ObservedObject探究
SwiftUI9/6/2023

前言

近期忙于重构代码,尝试落实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中的计数都被清零。

Reference

  1. SwiftUI数据流之StateObject& ObservedObject探讨