先理解 Property Wrapper 是什么
@propertyWrapper 就是让你可以自定义 @ 修饰符 的机制。 @State、@Binding 这些不是Swift内置的魔法,它们本质上就是普通的 struct,只不过被 @propertyWrapper 修饰了,所以才能用 @ 语法来用。 能理解吗?是不是还是很难理解,没事我写一个例子你就能理解了
swift
假设你有一个属性,每次读取它都想打印一条日志:
var age: Int = 18
var age: Int = 18 {
didSet { print("age 变了,新值是 \(age)") }
}
但如果你有 100 个属性都需要这个功能呢?你要写 100 次 `didSet`?
Property Wrapper 就是用来解决这个问题的
你可以把"通用的包装逻辑"封装起来,然后像帖标签一样贴到任何属性上。
swift
// 第一步:定义一个 Property Wrapper
@propertyWrapper
struct Logged {
private var value: Int
// initialValue 参数后面可以跟很多参数,自定义
init(initialValue: Int) {
self.value = initialValue
}
var wrappedValue: Int {
//这里的get 和set 我们可以自定义任何我们想要的操作,比如有多个参数我们可以把这些参数拼接起来返回等等
get { value }
set {
print("值变了,新值是 \(newValue)") // 通用逻辑写在这里
value = newValue
}
}
}
// 第二步:像贴标签一样使用它
@Logged var age = 18
@Logged var score = 100
// 现在 age 和 score 改变时,都会自动打印日志
age = 20 // 打印:值变了,新值是 20
score = 99 // 打印:值变了,新值是 99
所以 @propertyWrapper 本质上就是
把"对属性的操作逻辑"打包成一个 struct,然后用 @ 语法贴到属性上,让这个属性自动拥有那些逻辑。
回到 @State
@State 干的事情无非就是:
swift
@propertyWrapper
public struct State<Value> {
// 1. 让你能直接赋初始值
public init(initialValue value: Value)
// 2. 你平时用 brain 读写的就是这个 (这里set 之后苹果偷偷的去给你刷新了UI)
public var wrappedValue: Value { get nonmutating set }
// 3. 你用 $brain 拿到的就是这个(一个 Binding)
public var projectedValue: Binding<Value> { get }
}
@State 非常适合 struct 或者 enum 这样的值类型,它可以自动为我们完成从状态 到 UI 更新等一系列操作。但是它本身也有一些限制,我们在使用 @State 之前,对 于需要传递的状态,最好关心和审视下面这两个问题:
- 这个状态是属于单个 View 及其子层级,还是需要在平行的部件之间传递和使 用?@State 可以依靠 SwiftUI 框架完成 View 的自动订阅和刷新,但这是有 条件的:对于 @State 修饰的属性的访问,只能发生在 body 或者 body 所调 用的方法中。你不能在外部改变 @State 的值,它的所有相关操作和状态改变 都应该是和当前 View 挂钩的。如果你需要在多个 View 中共享数据,@State 可能不是很好的选择;如果还需要在 View 外部操作数据,那么 @State 甚至 就不是可选项了。
- 状态对应的数据结构是否足够简单?对于像是单个的 Bool 或者 String, @State 可以迅速对应。含有少数几个成员变量的值类型,也许使用 @State 也还不错。但是对于更复杂的情况,例如含有很多属性和方法的类型,可能其 中只有很少几个属性需要触发 UI 更新,也可能各个属性之间彼此有关联,那 么我们应该选择引用类型和更灵活的可自定义方式。