众所周知,Swift 是一门类型安全的语言,它会经过编译器报错来阻止你代码中不安全的行为。好比变量必须在使用以前声明、变量被销毁以后内存不能在访问、数组越界等问题。数组
Swift 会经过对于修改同一块内存,同一时间以互斥访问权限的方式(同一时间,只能有一个写权限),来确保你的代码不会发生内存访问冲突。虽然 Swift 是自动管理内存的,在大多数状况下你并不须要关心这个。但理解何种状况下会发生内存访问冲突也是十分必要的。安全
首先,来看一下什么是内存访问冲突。markdown
当你设值或者读取变量的值得时候,就会访问内存。app
var age = 10 // 写权限
print(age) // 读权限
复制代码
当咱们对同一块内存,同时进行读写操做时,会产生不可预知的错误。好比上面的 age,假如在你读取它值的期间有别的代码将它设为 20,那么你读取到的有多是 10,也有多是 20。这就产生了问题。async
内存访问冲突:对同一块内存,同时进行读写操做,或者同时进行多个写入操做时,就会形成内存访问冲突。ide
了解了什么是内存访问冲突,下面来看下什么状况下回形成内存访问冲突。函数
当 In-Out 参数为全局变量,而且该变量在函数体内被修改时,就会形成内存访问冲突。好比下面的代码:ui
var age = 10
func increment(_ num: inout Int) { // step1
num += age // step2
}
increment(&age)
复制代码
increment(:) 在整个函数体内,对全部的 In-Out 参数都有写权限。在上述代码中,step1 已经得到了 age 的写权限,而 step2 有获得了 age 的读权限,这样就形成了同一块内存,同时进行了读写操做。从而形成了内存访问冲突。spa
上面的问题能够经过将 age 拷贝一份来解决:线程
// step1
var copyOfAge = age
increment(©OfAge)
age = copyOfAge
复制代码
step1 将 age 的值拷贝到另外一块内存上,这样在函数体内就是存在对 age 的读权限和对 copyOfAge 的写权限,由于 age 和 copyOfAge 是两块内存,因此就不会形成内存访问冲突。
对于结构体的 mutating 函数来讲,它整个函数体都有 self 的写权限。
struct Person {
var age: Int
mutating func increment(_ num: inout Int) {
age += num
}
}
var p1 = Person(age: 10)
p1.increment(&p1.age)
复制代码
上述的代码编译器会报错:Overlapping accesses to 'p1', but modification requires exclusive access; consider copying to a local variable
。很明显这是一个内存访问冲突。
In-Out 参数得到了 p1 的写权限;mutating 函数也得到了 p1 的写权限。同一块内存,同时有两个写操做。形成内存访问冲突。能够经过同上的拷贝操做来解决。
对于结构体、枚举、元祖等值类型来讲,修改它们的属性就至关于修改它们整个的值。好比下面的代码:
func increment(_ num1: inout Int, _ num2: inout Int) {
print(num1 + num2)
}
var tuple = (age: 10, height: 20)
increment(&tuple.age, &tuple.height)
复制代码
&tuple.age
拿到了 tuple 的写权限,&tuple.height
又拿了 tuple 的写权限。同一块内存,同时有两个写操做。形成内存访问冲突。
这个问题能够经过局部变量来解决:
func someFunction() {
var tuple = (age: 10, height: 20)
increment(&tuple.age, &tuple.height)
}
复制代码
由于在 someFunction() 函数里,age 和 height 没有产生任何的交互(没有在其期间去读取或者写入 age 和 height),因此编译器能够保证内存安全。
PS:关于评论区的问题,在 someFunction() 函数里没有任何交互是什么意思?
答:在someFunction() 里,编译器能够保证没有别的线程来读取或者修改 tuple。所以,能够保证内存安全。而对于全局变量,编译器没法保证是否有别的线程在读取或者修改。
下面的代码就是在函数体内有交互的代码,虽然是局部变量,但涉及多个线程修改 tuple 的值,所以会形成内存访问冲突:
func someFunction() {
var tuple = (age: 10, height: 20)
DispatchQueue.main.async {
tuple.age += 10
}
DispatchQueue.main.async {
increment(&tuple.age, &tuple.height)
}
}
复制代码