SwiftUI属性包装器

151 阅读4分钟

SwiftUI属性包装器

SwiftUI的状态管理使用 @State @Publiced @StateObejct @observedObject@EnvironmentObject 等 property wrapper, 根据职责和作用范围不同,它们各自的适用场景也有区别

@State

@State 用于在视图中声明可变的状态属性,并自动更新视图, 一般来说它所修饰的都应该是 struct 值,并且不应该被其他的 view 看到。当使用 @State 标记属性时,在 SwiftUI 内部会被自动转换为一对 setter 和 getter,对这个属性进行赋值的操作将会触发 View 的刷新,它的 body 会被再次调用,底层渲染引擎会找出界面上被改变的部分,根据新的属性值计算出新的 View,并进行刷新。

struct ContentView: View {
    @State private var count: Int = 0
 
    var body: some View {
        Button(action: {
            count += 1
        }) {
            Text("Count: \(count)")
        }
    }
}
@Publiced

@Published 是 SwiftUI 中最有用的属性包装器之一,它使我们能够创建可观察的对象,这些对象会在发生更改时自动宣布。 SwiftUI 将自动监视此类更改,并重新调用所有依赖于数据的视图的 body 属性,

@Publiced属性包装器是类约束的,并且仅适用于类的属性.

需要将@Published与ObservableObject协议结合使用

@StateObject 


@StateObject 属性包装器保证对象只会被创建一次,@StateObject 基本上来说就是一个针对 class 的 @State 升级版,主要用来补充

@ObservedObject状态管理的不足,保证class 实例不会随着 View 被重新创建

class User: ObservableObject {
    @Published var name: String = "Tom"
    @Published var age: Int = 18
}

struct ContentView: View {
    @StateObject private var user = User()
    var body: some View {
        Button("Change User Name") {
            user.name = "Leborn"
        }
        Text("Current User:\(user.name)")
        Button("Add User Age") {
            user.age += 1
        }
        Text("Current User Age:\(user.age)")
	}
}

@ObservedObject


@ObservedObject 属性包装器,以便视图可以监视外部对象的状态,并在重要内容发生变化时得到通知, @ObservedObject 不管存储,会随着 View 的创建被多次创建

重要说明:每个对象只能使用 @StateObject 一次,无论在哪个视图中创建对象都应使用 @StateObject。 共享对象的所有其他视图应使用@ObservedObject

class User: ObservableObject {
    @Published var name: String
    @Published var age: Int = 18
    init(name: String) {
        self.name = name
        print("用户: \(name) init")
    }
}

struct ContentView: View {
    @ObservedObject private var user = User(name: "小明")
    var body: some View {
        Button("增加年龄") {
            user.age += 1
        }
        Text("当前用户:\(user.name)  -- \(user.age)")
        UserView()
    }
}

struct UserView: View {
    @ObservedObject private var user = User(name: "小米")
    var body: some View {
        Button("减少年龄") {
            user.age -= 1
        }
        Text("当前用户:\(user.name) -- \(user.age)")
    }
}

观察上面的代码, 对象User小明对象,不是随着view的创建而创建 这个时候使用@ObservedObject 和 @StateObject 达成效果差不多,

User小米对象,使用@ObservedObject在View和 Model之间添加订阅关系。点击增加年龄按钮,使ContenView中的状态发生变化,

ContentView.body被重新求值时,UserVIew就会重新生成,其他的User也一起重新生成,会造成性能消耗和状态消失

把UserView中的 @ObservedObject改成 @StateObject, 相当于把UserView的状态全面托管给SwiftUI内部处理,

保证这个UserView对象不会随着 ContenView的状态改变 被重新创建

struct UserView: View {
    @StateObject private var user = User(name: "小米")

@EnvironmentObject


@EnvironmentObject 属性包装器,是用于在当前视图中与上层视图经环境传递的 ObservableObject 实例之间创建关联的属性包装器。它提供了一种便捷的方式在不同的视图层级中引入共享数据,而无需显式地通过每个视图的构造器传递

使用 @@EnvironmentObject 前,必须确保已在视图层级的上游提供了相应的实例( 通过 .environmentObject 修饰器 ),否则将导致运行时错误。 它对视图的更新触发条件与@StateObject@ObservedObject 一样。 与 @ObservedObject 一样,@EnvironmentObject` 支持动态切换关联的实例。

struct ContentView: View {
    @State private var items = [User("小明"), User("小花")]
    var body: some View {
        VStack {
            Button("Update User") {
                items.reverse()
            }
            UserView()
                .environmentObject(items.first!)
        }
    }
}

struct UserView: View {
    @StateObject private var user = User(name: "小米") // 动态切换关联的实例
    var body: some View {
        VStack {
            Text(user.name)
        }
    }
}

总结 @StateObject@ObservedObject@EnvironmentObject 专用于关联符合 ObservableObject 协议的实例。 虽然在某些情形下 @StateObject 可以替代 @ObservedObject 并提供相似的功能,但它们各自有独特的使用场景。@StateObject 通常用于创建和维护实例,而 @ObservedObject 用于引入和响应已存在的实例。 在 iOS 17+ 的环境中,如果应用主要依赖于 Observation 和 SwiftData 框架,那么这三个属性包装器的使用频率可能会相对较低。 @Environment 提供了一种相对更安全的方法来引入环境数据,因为它可以通过 EnvironmentValue 提供默认值。这减少了因遗漏数据注入而导致的应用崩溃风险。 在 Observation 框架的背景下,@State@Environment 成为了最主要的属性包装器。无论是值类型还是 @Observable 实例,都可以通过这两种包装器引入视图。 自定义 Binding 提供了强大的灵活性,允许开发者在数据源和依赖于 Binding 的 UI 组件之间以简洁的代码实现复杂逻辑。