State
- 用来存储和观察一个Value Type的变化。这里的value是一个Generic。为什么要强调是一个Value Type?因为在SwiftUI中,View是struct(结构体),画面是在它的计算属性body里面产生的,我们没有办法从计算属性里面去修改自己的值。所以,我们才要把我们想要改变的值存到View以外的地方。State就是帮助我们做这件事情,把这笔资料存在另一个地方管理,并且在它管理的值发生变化的时候,自动通知画面进行更新。
- thread-safe。同时,它也自带thread-safe的存取,所以不用担心在不同线程同时修改它的问题。
- State不该被用任何形式传递。State应该只属于建立它的那一个View,不可以有其他任何地方能够存取到这个State本身。这是为了让SwiftUI正确地帮你管理这个值,让这个State的生命周期跟着这个画面。要做到这点很简单,只要做两件事,第一是把该State的属性设为private,这样你就不会再这个struct之外的地方去启动它。如果你需要一个动态的初始值,就需要自己写init,在这个struct的里面,去启动它的底线版本,这样就可以避免从外部启动。第二是不要使用底线版本的属性,也就是这个State本身。基本上所有的Property Wrapper你都不应该要使用他的底线版本。如果一个属性包装器允许你操作它本身的话,它会把自己透过projectedValue传出来给你用。但是State用$符号投射出来的让我们使用的是Binding
- Projected value是Binding。这个Binding就是连接到State里面打包的值。注意,是直接连接到State里面的Value,所以实际上对外部来说,它就只知道这个Value,它们并不会知道这个State的存在。
Binding
Binding同样有一个Generic的类型叫做,这个Value可以是任何类型。
-
包含两个closure,在Binding里面包括两个closure。一个是get,它会回传给你一个Value,另一个是set,需要你提供给它一个Value,接着他就会这个value值做点什么。看着这个get,set你就应该想到计算属性。Binding就是一个可以传来传去的计算属性。
-
通常Binding用来传递对一个Value Type资料修改的方法。下面举一个例子:
struct User { let name: String var creditCard: CreditCard}struct CreditCard { var balance: Int var cardNumber: String var expiredDate: Date}let card = CreditCard(balance: 1000)var user = User(name: "Jane", creditCard: card)user.creditCard.balance -= 100print(user.creditCard.balance)print(card.balance)
代码解读:首先创建了一个User结构体,然后创建了一个CreditCard结构体。然后建立了两个实例,一个是card实例,一个是user实例。user实例中的属性creditCard引用card建立了自己的副本,所修改的也是user所拷贝的副本,所以-100,副本还剩900。而card.balance直接打印结构体CreditCard,值不变,原始值还是1000。
但是,这个结果并不是我们想要的,我们希望两个印出来身下的都是900。第一种方法是,把信用卡变成Reference Type,这样不管在哪里都是改同一张卡。第二种方法就是使用Binding,其实也算是用Reference解决,因为closure也是Reference Type,如果我们改用Binding写,直接在money前面加上@Binding就好了。(@Binding通常在SwiftUI框架中使用,如果在普通的Swift结构体中使用,考虑使用Class或者inout。)
-
Binding也有自己的Projected Value,也就是Binding(~~它自己~~)。当一个Property Wrapper的Projected Value是它自己的时候,就代表你可以安心的操作这个Property Wrapper,这也就是Binding的最大用途。让我们可以把它传来传去。
Environment
- 用来读取一些系统在App启动时建立的“环境变数”。例如:字体大小、颜色模式。
- 这些环境变数被改动时,相关的画面都会一起更新。
- 只能读取,不能写入。
- 启动方式时放入一个EnvironmentValues的KeyPath。