09 Oct 2017 by 晓晨
下面是一段 Swift 的代码,代码做了简化。
class SomeClass {
...
var accumulator: Double?
func someFunc() {
if let accumulator = accumulator { // A
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(function: function, firstOperand: accumulator) // C
}
}
private func performPendingBinaryOperation() {
accumulator = pendingBinaryOperation.perform(with: accumulator)
}
...
}
这段代码的问题在于,C 行中传入的参数 accumulator
是 A 行通过 optional binding 获得的成员变量 accumulator
的本地拷贝,但是 B 行执行的函数 performPendingBinaryOperation
已经修改了成员变量 accumulator
,而 C 行使用的 accumulator
的本地拷贝在这里已经过时了。
如果这里 A 行使用的是 guard 语句,可能更难看出问题。
class SomeClass {
...
var accumulator: Double?
func someFunc() {
guard let accumulator = accumulator else { // A
return
}
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(function: function, firstOperand: accumulator) // C
}
private func performPendingBinaryOperation() {
accumulator = pendingBinaryOperation.perform(with: accumulator)
}
...
}
问题的根源来自 optional binding 对成员变量的本地拷贝在使用时过期了。
如果换成调用函数时对实参的形参拷贝也有可能出现类似的情况。
class SomeClass {
...
var accumulator: Double?
func someFunc(accumulator: Double) { // A
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(function: function, firstOperand: accumulator) // C
}
private func performPendingBinaryOperation() {
accumulator = pendingBinaryOperation.perform(with: accumulator)
}
...
}
假如 someFunc
在被调用时传入的是成员变量 accumulator
,那么在函数内 performPendingBinaryOperation
执行后,函数内的形参 accumulator
和传入的成员变量 accumulator
已经不一致。
以上情况,不管是 optional binding(if 或 guard),还是函数参数传递,本质上是,在拷贝变量和对新变量的使用之间,发生了对原变量的修改,即,
class SomeClass {
...
var accumulator: Double?
... {
let newAccumulator = accumulator // A
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(function: function, firstOperand: accumulator) // C
}
private func performPendingBinaryOperation() {
accumulator = pendingBinaryOperation.perform(with: accumulator)
}
...
}
B 行即是上面提到的对原变量的修改。因为被封装在了函数里,所以很不明显。
理想情况下,在拷贝变量和对新变量的使用之间,如果调用了任何函数,且该函数或者该函数内部调用的函数(包括 computing property setter)实现中,修改了原变量,则应该给出警告。
解决方案:
还在想,没想出非常可行的方法。欢迎提供思路。 (SwiftLint 似乎不适合检查这么复杂的情况)