假设我有以下对象:
class Dog {
let name: String
var stats: Stats?
func doSomething() { /*...*/ }
}
struct Stats {
var weight: Double
var age: Int
func ageEquivalence() -> Int { /*...*/ }
}
...我想在Dog
实例上执行一些计算。但是,我想原子地这样做。换句话说,我想保证执行所有代码,或者执行 no 代码。
为了澄清问题,首先,我们假设Stats
是class
。在这种情况下,这个问题很容易解决:
func updateAge(_ dog: Dog) {
guard let stats = dog.stats else {
return
}
stats.age += 1 // A
dog.doSomething() // B
sendNotification(stats.ageEquivalence()) // C
}
如果存在统计数据,它将会执行A-C行,或者什么也不做。
现在让我们回到原来的问题,假设Stats
再次是struct
。如果我尝试运行上面的代码,首先我必须将let stats
更改为var stats
,否则编译器会抱怨我尝试更新常量。
func updateAge(_ dog: Dog) {
guard var stats = dog.stats else {
return
}
stats.age += 1 // A
dog.doSomething() // B
sendNotification(stats.ageEquivalence()) // C
}
但是现在我们遇到stats
是dog.stats
副本的问题,所以当我更新A行的年龄时,我实际上并没有修改原来的狗#39;年龄。
我们的下一个方法可能如下所示:
func updateAge(_ dog: Dog) {
dog.stats?.age += 1 // A
dog.doSomething() // B
if let stats = dog.stats {
sendNotification(stats.ageEquivalence()) // C
}
}
这种方法的问题在于,如果doSomething
具有设置stats = nil
的副作用,我们就会运行A& A行。 B,但不是C。
解决此问题的最佳方法是什么?
似乎问题的关键在于这一行:
dog.stats?.age += 1
为了修改struct
,我们必须对原始对象采取行动,这意味着我们必须使用optional chaining。这不能与guard var
一起使用,因为这会创建原始struct
的副本,并且原始struct
可能会因执行任何点的副作用而发生更改