Swift中常用的四种属性包装器
Swift中常用的四种属性包装器

Swift中常用的四种属性包装器

@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从环境中直接获取对象。

所以,各种模式都有应用场景,可以根据需求选择合适的修饰符让数据管理更加方便和灵活。

相关文章

  1. 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/
  2. 《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/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注