didSet
中更改值class
和 struct
中的 didSet
didSet
是 Swift
中比较常用的属性观察器,但是使用它需要注意一些问题。
didSet
, 很容易被理解为:在设置新的值之后,didSet 马上就会被调用
。
Swift
官方文档也是这么写的:在新值被存储后,didSet 会被立即调用
。
果真是我们理解的这样吗?
在初始化一个类型的实例时,属性中的 didSet
会被调用吗? 让我们来看一个例子:
class User {
var name: String {
didSet {
print(name)
}
}
init(name: String) {
self.name = name
}
}
let user = User(name: "0")
user.name = "1"
请猜一下,上面的代码会输出什么内容?
输出内容: 1
,为什么没有输出 0
呢?
在回答这个问题之前,让我们再来看一个例子:
class User {
var name: String {
didSet {
print(name)
}
}
init(name: String) {
self.name = name
}
}
class Admin: User {
override init(name: String) {
super.init(name: name)
if name == "-" {
self.name = "No Name!"
}
}
}
let admin = Admin(name: "-")
admin.name = "admin"
请猜一下,上面的代码会输出什么内容?
输出内容:
No Name!
admin
为什么在子类的 init
方法中修改 name
属性就可以触发 didSet
?
Swift
官方文档中是这样描述的:
根据以上描述,可以总结出:
更改属性的值的操作触发属性观察器 willSet
和 didSet
的条件是 属性已完成初始化
(已经被设置过一次)。
在 didSet
中更改值,是否会导致死循环?这个问题,有些人不能自信地给你答案~
还是前面的那个例子,不过有一点点小改动:
class User {
var name: String {
didSet {
print(name)
name = "Fixed name" // 更改name
}
}
init(name: String) {
self.name = name
}
}
let user = User(name: "0")
user.name = "1"
请问,输出内容是什么?
其实,还是: 1
。
在 didSet
中更改值,不会导致死循环。刚刚在 didSet
中设置的新值,会覆盖之前的值。
让我们来观察两个例子。
class HealthInfo { // 请注意,这是一个 class
var height: Float {
didSet {
print(height)
}
}
var weight: Float
init(height: Float, weight: Float) {
self.height = height
self.weight = weight
}
}
class User {
var healthInfo: HealthInfo { // 请注意观察这个属性的 didSet
didSet {
print(healthInfo)
}
}
init(healthInfo: HealthInfo) {
self.healthInfo = healthInfo
}
}
let user = User(healthInfo: .init(height: 160, weight: 50))
user.healthInfo.height = 162
user.healthInfo.height = 163
第二个例子仅仅是把 HealthInfo
从 class
改为了 struct
,其余代码没有任何改动。
struct HealthInfo { // 请注意,这是一个 struct
var height: Float {
didSet {
print(height)
}
}
var weight: Float
init(height: Float, weight: Float) {
self.height = height
self.weight = weight
}
}
class User {
var healthInfo: HealthInfo { // 请注意观察这个属性的 didSet
didSet {
print(healthInfo)
}
}
init(healthInfo: HealthInfo) {
self.healthInfo = healthInfo
}
}
let user = User(healthInfo: .init(height: 160, weight: 50))
user.healthInfo.height = 162
user.healthInfo.height = 163
猜猜看,这两个例子的输出是什么?
前面的例子输出:
162.0
163.0
后面的例子输出:
162.0
HealthInfo(height: 162.0, weight: 50.0)
163.0
HealthInfo(height: 163.0, weight: 50.0)
这是为什么呢?难道,HealthInfo
实例已经变了吗?
让我们修改一下示例代码,打印 user.healthInfo
的内存地址看看:
// ...
func printAddressOf<T>(_ t: inout T) {
withUnsafePointer(to: t) { pointer in
print(pointer)
}
}
var user = User(healthInfo: .init(height: 160, weight: 50))
printAddressOf(&user.healthInfo)
user.healthInfo.height = 162
printAddressOf(&user.healthInfo)
user.healthInfo.height = 163
printAddressOf(&user.healthInfo)
输出内容:
0x00007ffeedd8da30
HealthInfo(height: 160.0, weight: 50.0)
162.0
HealthInfo(height: 162.0, weight: 50.0)
0x00007ffeedd8da30
HealthInfo(height: 162.0, weight: 50.0)
163.0
HealthInfo(height: 163.0, weight: 50.0)
0x00007ffeedd8da30
HealthInfo(height: 163.0, weight: 50.0)
所以,user.healthInfo
本身的地址并没有修改。但是 Swift
依然会在修改结构体的值时为你调起属性观察器。
如果 在方法中修改属性
,是否也有类似的情况呢?
让我们来看一下最后两个示例:
class HealthInfo { // 定义为一个 class
var height: Float {
didSet {
print(height)
}
}
var weight: Float
func increaseWight() { // 增加了一个修改属性的方法
weight += 1
}
init(height: Float, weight: Float) {
self.height = height
self.weight = weight
}
}
class User {
var healthInfo: HealthInfo {
didSet {
print(healthInfo)
}
}
init(healthInfo: HealthInfo) {
self.healthInfo = healthInfo
}
}
var user = User(healthInfo: .init(height: 160, weight: 50))
user.healthInfo.increaseWight()
以下是采用 struct
实现的示例:
struct HealthInfo { // 定义为一个 struct
var height: Float {
didSet {
print(height)
}
}
var weight: Float
mutating func increaseWight() { // 在 mutating 方法中修改属性
weight += 1
}
init(height: Float, weight: Float) {
self.height = height
self.weight = weight
}
}
class User {
var healthInfo: HealthInfo {
didSet {
print(healthInfo)
}
}
init(healthInfo: HealthInfo) {
self.healthInfo = healthInfo
}
}
var user = User(healthInfo: .init(height: 160, weight: 50))
user.healthInfo.increaseWight()
可能你已经猜到了。
前一个示例不输出任何内容,而后一个示例会输出 HealthInfo(height: 160.0, weight: 51.0)
。
所以,请记住:struct
是值类型,修改 struct
中的属性就是在改变自身,所以会触发观察这个 struct
的属性观察器,而 class
不会。
因此,我们可以肯定这个结论在 enum
中也是成立的。
请看以下示例(我保证,这是最后一个示例了!):
enum Type {
case good, bad
mutating func switchSelf() {
self = self == .good ? .bad : .good
}
}
var aType: Type = .good { // 其实属性观察器可以这样定义,不需要嵌入到某种类型中
didSet {
print("didSet")
}
}
aType.switchSelf()
现在,请猜一猜输出内容是什么?
没错,输出内容是:didSet
。
恭喜你,又越过了几个坑 ~~~
参考内容 :
Bug or intended? “thing?.property = something” will trigger didSet observer even if thing is nil, but ONLY IF thing is a struct type
Printing a variable memory address in swift
觉得不错?点个赞呗~
转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog