Swift
内存安全
本文详细介绍了 Swift 中的内存安全机制,重点讲解了内存访问冲突(Conflicting Access to Memory)的产生原因、典型场景(如 In-Out 参数、结构体属性访问)及其避免方法。
Swift 默认保证代码的内存安全。它会自动处理内存管理(通过 ARC),强制变量初始化,并检查数组越界。然而,在某些特定情况下,你需要理解并避免内存访问冲突(Conflicting Access to Memory)。
内存访问冲突
当两处代码同时访问同一块内存,且至少有一处是写操作时,就会发生内存访问冲突。这通常发生在多线程环境中,但在单线程中也可能发生(例如,在函数调用期间访问同一变量)。
冲突发生的三个条件(必须同时满足):
- 至少有一个是写访问。
- 它们访问的是同一块内存。
- 它们的访问时间重叠。
In-Out 参数的访问冲突
函数的 In-Out 参数在函数执行期间具有长期的写访问权限。如果你在调用函数时,将同一个变量同时作为多个 In-Out 参数传递,或者在函数内部访问该变量,就会产生冲突。
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// 错误:stepSize 的读访问与 increment(_:) 对 stepSize 的写访问冲突解决办法是显式地拷贝一份副本:
var copyOfStepSize = stepSize
increment(©OfStepSize)
stepSize = copyOfStepSize
// 正确方法中 self 的访问冲突
在结构体的变异方法(mutating method)中,self 具有对整个实例的写访问权限。如果你将 self 的某个属性作为 In-Out 参数传递给另一个函数,就会产生冲突。
struct Player {
var name: String
var health: Int
var energy: Int
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // 正确
oscar.shareHealth(with: &oscar)
// 错误:oscar 在 shareHealth 中被写访问(self),同时作为参数被写访问(teammate)属性的访问冲突
对于值类型(如结构体、元组),修改其任何一部分(如属性、元素)都需要对整个值进行写访问。这意味着你不能同时对同一个结构体的不同属性进行重叠的写访问。
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// 错误:同时访问 playerInformation 的不同属性注意:如果上述代码是在局部作用域(如函数内部)中,Swift 编译器可以证明这种访问是安全的(因为局部变量不会被其他线程访问),从而允许这种操作。但在全局作用域或类属性中,这是被禁止的。
总结
- Swift 能够静态检测大多数单线程内存冲突。
- 避免将同一变量同时作为多个 In-Out 参数传递。
- 避免在变异方法中将
self作为 In-Out 参数传递。 - 理解值类型的整体突变特性。
在 GitHub 上编辑
上次更新于