@State、@StateObject 和@ObservedObject和@EnvironmentObject都是 SwiftUI 中用于管理视图状态的属性包装器,但它们的使用场景和功能有所不同。下面详细说明它们的区别:
@State
作用: 用于声明一个简单的值类型的状态,比如 Int、String、Bool 等。
生命周期: @State 属性的生命周期由 SwiftUI 视图自己管理。当视图销毁时,@State 变量也会被销毁。
适用场景: 当状态只在当前视图内部使用,且需要在视图重新渲染时保持时,可以使用 @State。
注意事项: 只能用于值类型(struct),不能直接用于引用类型(class)。
struct CounterView: View {
@State private var count = 0 // 使用 @State 创建一个本地状态
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increase") {
count += 1 // 改变状态值,视图会自动更新
}
}
}
}
@StateObject
作用: 用于管理类(引用类型)对象的状态,并且由 SwiftUI 视图控制其生命周期。保证对象在视图多次重绘时不会被重新创建。
生命周期: @StateObject 在视图第一次创建时初始化,并在视图存在期间保持引用。视图销毁时,@StateObject 也会被销毁。
适用场景: 当你需要在视图中创建和管理一个 ObservableObject 实例时,应使用 @StateObject。通常用于那些只在当前视图使用的对象。
注意事项: 应该只在对象的 创建位置 使用 @StateObject。
class Counter: ObservableObject {
@Published var count = 0
}
struct CounterView: View {
@StateObject private var counter = Counter()
var body: some View {
VStack {
Text("Count: \(counter.count)")
Button("Increment") {
counter.count += 1
}
}
}
}
@ObservedObject
作用: 用于监听已经在其他地方创建的 ObservableObject 实例的状态更新。
生命周期: @ObservedObject 并不负责对象的生命周期管理,只是监听对象。当对象发生变化时,视图会自动更新。
适用场景: 当对象在其他地方(例如父视图或更高级的管理器)已经存在时,可以使用 @ObservedObject 传递给子视图进行观察。
注意事项: 不会创建对象,只是观察已有的对象。
class CounterModel: ObservableObject {
@Published var count = 0
}
struct ParentView: View {
@StateObject private var counter = CounterModel()
var body: some View {
ChildView(counter: counter)
}
}
struct ChildView: View {
@ObservedObject var counter: CounterModel
var body: some View {
VStack {
Text("Count: \(counter.count)")
Button("Increase") {
counter.count += 1
}
}
}
}
@EnvironmentObject
作用: 用于在视图层级中注入和共享 ObservableObject 实例,使其可以在视图树的任何地方访问,而无需显式传递。
生命周期: 由上层视图(通常是 @StateObject)管理。@EnvironmentObject 本身并不创建对象,而是从环境中读取已有的对象。
适用场景: 当你需要在多个视图之间共享数据时,@EnvironmentObject 是一个便捷的选择。适合全局或多个子视图需要访问的对象。
注意事项: 使用 @EnvironmentObject 的视图必须在上层视图的 .environmentObject() 中进行注入,否则会导致运行时崩溃。
class Counter: ObservableObject {
@Published var count = 0
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ParentView().environmentObject(Counter())
}
}
}
struct ParentView: View {
var body: some View {
ChildView()
}
}
struct ChildView: View {
@EnvironmentObject var counter: Counter
var body: some View {
VStack {
Text("Count: \(counter.count)")
Button("Increment") {
counter.count += 1
}
}
}
}
区别总结
class Counter: ObservableObject { }
数据类型:@State用于值类型(Int、String等)、@StateObject、@ObservedObject、@EnvironmentObject全都用于引用类型(ObservableObject)。
生命周期管理:@State被SwiftUI管理、@StateObject被SwiftUI视图管理、@ObservedObject被外部视图管理、@EnvironmentObject被上层环境控制。
对象的创建和初始化:@State在视图中声明简单状态、@StateObject在视图中创建并管理对象、@ObservedObject从父视图传入对象、子视图(自身)不创建对象、@EnvironmentObject从环境读取对象。
使用场景:@State用于局部的、简单的值类型状态管理、@StateObject用于在视图中需要初始化的引用类型对象、@ObservedObject用于观察和相应外部传入的对象、@EnvironmentObject在整个视图层级中共享数据,不显式传递。
总结
@State
对于简单的数据结构或者struct结构,可以使用@State属性包装器,当涉及类Class时,就不要用@State,因为它观察不到类变化,所以就会出现类的实例变化时,SwiftUI并不会重新绘制视图。
@StateObject和@ObservedObject
当涉及到传参时,都需要类符合ObservableObject协议以及观察的属性需要声明为@Published。
class Counter: ObservableObject {
@Published var count = 0
}
通常情况下,都可以考虑使用@StateObject和@ObservedObject,通过@StateObject在父视图中创建和管理对象的声明周期,然后传参给子视图,在子视图中使用@ObservedObject:
struct ParentView: View {
@StateObject private var counter = Counter() // 创建对象并管理生命周期
var body: some View {
ChildView(counter: counter) // 将对象传递给子视图
}
}
struct ChildView: View {
@ObservedObject var counter: Counter // 引用父视图传递的对象
var body: some View {
VStack {
Text("Count: \(counter.count)")
Button("Increment") {
counter.count += 1
}
}
}
}
在这里@StateObject创建和管理对象的声明周期,@ObservedObject则引用@StateObject所创建的对象进行变化。
@StateObject就像一个硬盘的文件,而@ObservedObjectshi一个文件链接,后者引用或观察前者的数据进行更新。
当然,与其说硬盘上的文件,或许可以说内存上的文件,因为它会在视图销毁时,随之销毁。
@EnvironmentObject
@EnvironmentObject与前面的@StateObject和@ObservedObject功能类似,只是它在顶层注入后,可以直接访问。
就像宪法一样。只要国家颁布了宪法,省市可以参考使用、地方乃至村落都可以直接使用。
@EnvironmentObject 在应用或视图层级的顶层注入(例如 App 结构体中),之后任何子视图都可以直接通过 @EnvironmentObject 获取这个对象,无需显式传递。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ParentView().environmentObject(Counter()) // 注入环境对象
}
}
}
struct ParentView: View {
var body: some View {
ChildView() // 不用显式传递对象
}
}
struct ChildView: View {
@EnvironmentObject var counter: Counter // 直接从环境中获取对象
var body: some View {
VStack {
Text("Count: \(counter.count)")
Button("Increment") {
counter.count += 1
}
}
}
}
也不需要搭配@StateObject,因此在前面也看到了在@main入口文件处,给ParentView视图注入environmentObject之后。ParentView不需要显式传递对象,ChildView就可以直接通过@EnvironmentObject从环境中直接获取对象。
所以,各种模式都有应用场景,可以根据需求选择合适的修饰符让数据管理更加方便和灵活。
相关文章
- Swift通过 @EnvironmentObjec共享和传递数据:https://fangjunyu.com/2024/10/23/swift%e9%80%9a%e8%bf%87-environmentobjec%e5%85%b1%e4%ba%ab%e5%92%8c%e4%bc%a0%e9%80%92%e6%95%b0%e6%8d%ae/
- 《Swift 从实战到理论:@publish 和@ObservedObject》:https://fangjunyu.com/2024/10/22/swift-%e4%bb%8e%e5%ae%9e%e6%88%98%e5%88%b0%e7%90%86%e8%ae%ba%ef%bc%9apublish-%e5%92%8c-observedobject/