引言
在本篇文章之前的 iOS 入门系列(一)、iOS 入门系列(二)已经分别介绍了iOS 开发
的开发语言
、开发工具
以及开发框架
等内容;且所有内容均收集在 iOS 入门系列合集 中。在本篇将会对SwiftUI 框架
中的数据状态和数据流
进行介绍
让数据状态和数据流
开启我们入坑之旅的第二站吧~
State And Data Flow(数据状态和数据流)
在SwiftUI 框架
中是通过属性包装器
来管理数据的状态
、流动
和传递
的。数据状态和数据流
也是掌握并进行iOS 开发
需要了解的重点知识~
下面便是SwiftUI
中的属性包装器,接下来会一一对其进行介绍:
- @State
- @Published
- @StateObject
- @ObservedObject
- @EnvironmentObject
- @Environment()
- @binding
@State
用于在视图内部
管理和维护短暂的、局部的状态
@State
属性包装器用于在视图内部管理和存储单一数据值
,它通常用于表示视图的内部状态
或用户界面的临时状态
import SwiftUI
struct CounterView: View {
@State private var count: Int = 0
}
@State
直接在视图中声明变量后即可在视图内部管理和响应数据的变化,从而实现视图的动态更新,使用较为频繁
@Published
用于在ObservableObject 类型
中标记属性,使其成为可被观察
的属性
@Published
属性包装器通常用于在ObservableObject
类中声明被发布的属性,以便在属性值发生变化时通知订阅者
import SwiftUI
class User: ObservableObject {
@Published var username: String = "Guest"
}
直接在ObservableObject 类型
的对象中声明被发布的属性,然后通过@StateObject
、@ObservedObject
、@EnvironmentObject
来实现订阅这些属性
@StateObject
用于在视图中创建
并维护一个持久化的、全局的ObservableObject 实例
@StateObject
适用于在视图内部创建和管理对象,它会自动在视图销毁时销毁对象。这通常用于ViewModel
,因为ViewModel 对象
的生命周期通常与视图相同
下面是使用@StateObject
的案例:
class ViewModel: ObservableObject {
@Published var data: String = "Initial Data"
}
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
Text(viewModel.data)
.onTapGesture {
viewModel.data = "Updated Data"
}
}
}
可以看出,在view 视图
中通过@StateObject
声明的属性是在视图中自己创建的
@ObservedObject
用于在视图中引用
一个外部创建的ObservableObject 实例
@ObservedObject
适用于从外部传递对象给视图,并且希望视图能够观察该对象的变化。对象的生命周期不由视图管理,而是由传递者管理。适用于外部创建了一个对象并希望多个视图都能观察和使用它
class ViewModel: ObservableObject {
@Published var data: String = "Initial Data"
}
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
Text(viewModel.data)
.onTapGesture {
viewModel.data = "Updated Data"
}
}
}
struct ParentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
NavigationView {
NavigationLink(destination: ContentView(viewModel: viewModel)) {
Text("Go to ContentView")
}
}
}
}
可以看出,在view 视图
中通过@ObservedObject
声明的属性只是在视图中进行了声明,需要通过父视图
将对象传递给子视图
@EnvironmentObject
用于在整个应用程序
中共享一个ObservableObject 实例
@EnvironmentObject
适用于将对象在整个视图层次结构中自动传递给所有需要的视图。需要在应用程序的根视图
中将一个自定义的数据对象设置为环境对象
。然后多个视图
就能够共享
相同的数据,并在数据变化时进行自动更新
根视图设置环境对象
class UserModel: ObservableObject {
@Published var username: String = ""
@Published var age: Int = 0
}
@main
struct YourApp: App {
@StateObject private var userModel = UserModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(userModel)
}
}
}
@EnvironmentObject 声明对象
struct ContentView: View {
@EnvironmentObject var userModel: UserModel
var body: some View {
VStack {
Text("Username: \(userModel.username)")
Text("Age: \(userModel.age)")
}
}
}
@Environment()
用于在视图
中访问系统提供的环境变量
@Environment
属性包装器用于访问环境变量,这些变量由系统提供或在应用程序范围内设置
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
if colorScheme == .dark {
Text("Dark Mode")
.foregroundColor(.white)
.background(Color.black)
} else {
Text("Light Mode")
.foregroundColor(.black)
.background(Color.white)
}
}
}
通过系统提供的.colorScheme
变量来访问当前的颜色方案,@Environment()
在访问系统变量
时需要带上\
@binding
用于在视图之间
双向绑定属性,实现数据传递和同步
@Binding
属性包装器用于在父视图中创建一个绑定,然后将其传递给子视图,以便子视图可以修改父视图中的数据。这对于在视图层次结构中共享数据非常有用
import SwiftUI
struct ContentView: View {
@State private var isToggleOn = false
var body: some View {
ToggleView(isOn: $isToggleOn)
.padding()
}
}
struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Toggle", isOn: $isOn)
}
}
在父视图
中创建一个属性并将其标记为@State
或其他可修改的属性包装器
,然后将这个属性传递给子视图
中用@Binding
属性包装器声明的绑定属性
。子视图可以使用这个绑定属性
来读取
和修改
父视图中的属性值
拓展
-
在SwiftUI中,还有其他带有@符号的属性和特性,但它们却不是属性包装器。下面列举我目前使用到的:
-
视图构建:
@ViewBuilder
可以将
多个视图
作为闭包参数
传递给函数,然后在函数中使用这些视图构建UI 层次
-
函数特性:
@escaping
@escaping
主要用于函数参数的声明
,用于函数类型参数的标记,用来指示该闭包参数
是逃逸闭包
,会在函数执行结束后继续存在并被调用
-
-
为什么要存在这么多不同层级的传递数据的属性包装器?
这里拿
@ObservedObject
和@EnvironmentObject
对比说明,@EnvironmentObject
既然能在全局共享数据,那为什么还需要@ObservedObject
或者其他属性包装器
?虽然
@EnvironmentObject
在全局数据共享方面非常强大,但并不是所有情况都适合使用它,而@ObservedObject
在某些场景下仍然有其优势和用武之地:- 局部性:
@ObservedObject
更适用于视图的局部性数据共享
。如果数据只在某个特定的视图层次结构内使用,将其作为@ObservedObject
传递给子视图会更清晰和合适 - 粒度控制:
@ObservedObject
允许您在不同层次的视图中使用不同的数据对象,每个视图可以有自己的数据源,而@EnvironmentObject
会在整个应用程序中共享相同的数据对象 - 性能消耗:由于
@EnvironmentObject
是全局共享的,当应用程序中的多个视图都依赖于同一个@EnvironmentObject
时,可能会导致性能问题。每次数据更改时,所有依赖于该对象的视图都会重新计算,这可能会导致不必要的性能消耗 - 测试和解耦:使用
@ObservedObject
可以更容易进行单元测试,因为您可以更精确地控制要传递的数据对象。此外,使用@ObservedObject
可以更好地解耦视图,使其更加独立和可复用
综上所述,
@EnvironmentObject
在全局数据共享方面是非常强大的,但在一些特定场景下,使用@ObservedObject
可能更加合适,以达到更好的性能、测试和解耦效果 - 局部性:
小结
本文主要是针对SwiftUI
中属性包装器
对于视图及视图间
的数据状态和数据流
的管理,下面就对全文做一个简要总结
@State
和@Published
使用及理解起来相对容易,一个是在视图内
声明单一数据值
,一个是在ObservableObject 类型对象
中声明被发布的属性值
@StateObject
、@ObservedObject
在使用时可能会存在一些困惑,但只要牢记@StateObject
是在视图内创建ObservableObject 实例
,而@ObservedObject
则是在视图内引用ObservableObject 实例
即可
@ObservedObject
和@EnvironmentObject
在理解上也会存在一些疑惑,但只要明白:@ObservedObject
需要通过父视图将对象传递给子视图,而@EnvironmentObject
只需要在根视图中传递一次,之后所有视图都能共享这个对象;@ObservedObject
更适用于局部范围的数据共享,而@EnvironmentObject
更适用于全局范围的数据共享的概念后也能正确使用它们了
@EnvironmentObject
和@Environment()
虽然长得相似,但是也是不同的,@EnvironmentObject
用于在视图之间共享自定义数据对象,而@Environment
用于访问系统提供的环境变量
@binding
也同@State
、@Published
、@StateObject
、@ObservedObject
、@EnvironmentObject
、@Environment()
等属性包装器一样这些与数据状态、流动和传递相关,能实现在视图之间
创建双向绑定