衆所周知,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)
}
}
複製代碼