Swift didSet 为什么没有执行?🌚🌚🌚

| Swift , Bug

 

内容概览

  • 引言
  • 初始化时
  • didSet 中更改值
  • classstruct 中的 didSet

 
 

引言

 

didSetSwift 中比较常用的属性观察器,但是使用它需要注意一些问题。

didSet, 很容易被理解为:在设置新的值之后,didSet 马上就会被调用

Swift 官方文档也是这么写的:在新值被存储后,didSet 会被立即调用

图片alt

果真是我们理解的这样吗?

 
 

初始化时

 

在初始化一个类型的实例时,属性中的 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 官方文档中是这样描述的:

图片alt

 

根据以上描述,可以总结出:

更改属性的值的操作触发属性观察器 willSetdidSet 的条件是 属性已完成初始化 (已经被设置过一次)。

 
 

在 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 和 struct 中的 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

第二个例子仅仅是把 HealthInfoclass 改为了 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

觉得不错?点个赞呗~

本文链接:Swift didSet 为什么没有执行?🌚🌚🌚

转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog

评论区(期待你的留言)