前言
亂譯:不是規(guī)規(guī)矩矩的翻譯,主要目的是為了學(xué)知識(shí)。但也是無奈之舉,水平有限,我不會(huì)啊。有些地方我加入了自己的理解,關(guān)于新的知識(shí)點(diǎn)我會(huì)翻譯的詳細(xì)些,舊知識(shí)我就概括翻譯,不懂的就不翻譯。
官網(wǎng) : Memory Safety
Beginning
默認(rèn)情況下,Swift 會(huì)防止不安全的編碼行為發(fā)生。例如:
1 .確保變量在初始化之后才能使用
2 .內(nèi)存(對(duì)象)釋放 之后,是不可訪問的
3 .數(shù)組下標(biāo)會(huì)做越界檢測(cè)
Swift 可以確保同一內(nèi)存多個(gè)訪問不發(fā)生沖突。是這樣做到的:
把一個(gè)內(nèi)存地址改到一個(gè)唯一的地址。 啥,啥,啥!不會(huì)翻譯,好像是說,每一個(gè)訪問的代碼在修改對(duì)象之前,都會(huì)修改訪問對(duì)象的地址到一個(gè)只有它自己可以訪問的唯一的地址。 這樣就不沖突了(后面發(fā)現(xiàn)它這是針對(duì)單線程的,一開始,誤以為是多線程讀到這嚇一跳)
Swift 的這些內(nèi)存管理都是自動(dòng)的,如果我們的代碼包含了沖突,會(huì)有編譯時(shí)錯(cuò)誤或運(yùn)行時(shí)錯(cuò)誤,但是我們應(yīng)知其然知其所以然。這就是這篇文章的目的。
Understanding Conflicting Access to Memory 理解內(nèi)存訪問沖突
當(dāng)我們給變量賦值或向函數(shù)傳參時(shí),就會(huì)發(fā)生內(nèi)存訪問。如下代碼包含了內(nèi)存讀訪問和寫訪問
// one 被寫入內(nèi)存
var one = 1
// one 從內(nèi)存中讀出來
print("We're number \(one)!")
下面舉個(gè)了賬單的讀寫沖突的例子,舊知識(shí),老生常談的,不翻譯了,。。。
note
這里討論的是單線程的情況,不包含并發(fā)編程和多線程情況。
如果你是在單線程時(shí),寫了內(nèi)存訪問沖突的代碼,Swift會(huì)自動(dòng)的在編譯或運(yùn)行時(shí)報(bào)錯(cuò)。
如果你多線程編程請(qǐng)使用 Thread Sanitizer 幫助你檢測(cè)線程沖突。
Characteristics of Memory Access 內(nèi)存訪問的特征
相當(dāng)于介紹了內(nèi)存訪問:問自己什么是內(nèi)存訪問,便于理解。
- 至少一個(gè)在讀
- 多個(gè)同時(shí)寫
- 他們期間,發(fā)生重疊。讀的期間,寫的期間。
想一想,有些讀寫是瞬間的有些并不是瞬間的,不能理解看完后面的部分,就會(huì)明白
訪問有瞬間訪問和長期訪問之分,當(dāng)然,瞬間訪問是不會(huì)沖突。大部分內(nèi)存訪問都是瞬間訪問,例如下面的例子
func oneMore(than number: Int) -> Int {
return number + 1
}
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// Prints "2"
但是,重點(diǎn)!重點(diǎn)!重點(diǎn)!有幾種方式的內(nèi)存訪問是長期訪問,在這個(gè)期間,其他代碼可以執(zhí)行。
和瞬間訪問不同,對(duì)長期訪問來說,其他代碼是可以在長期訪問開始但沒有結(jié)束期間執(zhí)行的。我們把這叫做 overlap 覆蓋
長期訪問最基本的一個(gè)例子是在使用 in-out 參數(shù)時(shí)。
Conflicting Access to In-Out Paramters In-Out 參數(shù)訪問沖突
函數(shù)對(duì)它所有的 in-out 參數(shù) 持有長期寫的訪問,持有開始于非 in-out 參數(shù)的 evaluated (啥啥啥,不會(huì)翻譯了,就記住從函數(shù)開始到函數(shù)結(jié)束)(這是Swift語言決定的,我說的哈哈)
長期寫訪問的一個(gè)結(jié)論是 :你不能訪問通過 in-out 修飾傳過來的參數(shù),即使它本來可以訪問。例如下面的例子,你一運(yùn)行就 crash 報(bào)錯(cuò): Simultaneous accesses to 0x100006728, but modification requires exclusive access.
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// Error: conflicting accesses to stepSize
雖然 stepSize is a global variable, 本來可以訪問,但這里不能訪問了!看下圖很容易理解

一種解決方案是:對(duì) setpSize 顯示 copy
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(©OfStepSize)
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
長期寫訪問的另一個(gè)關(guān)于 in-out 參數(shù)的結(jié)論是:不可以對(duì)有多個(gè) in-out 參數(shù)的函數(shù)傳遞一個(gè)變量??蠢泳兔靼滓馑剂?/p>
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // OK
balance(&playerOneScore, &playerOneScore)
// Error: conflicting accesses to playerOneScore
Conflicting Access to self in Methods 方法中 self 的訪問沖突
結(jié)構(gòu)體的可變方法在被調(diào)用期間對(duì) self 持有讀的訪問。
extension Player {
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) // OK
如果,你把 oscar 作為參數(shù)傳給 sheareHealth(with:),會(huì)有沖突:
oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar
shareHealth 對(duì) self 有讀的訪問,in-out 對(duì) teammate 有讀的訪問。self 和 teammate 都是 oscar 產(chǎn)生 沖突!這一點(diǎn)原文的圖好像畫的有點(diǎn)問題,文字表述是對(duì)的。
Conflicting Access to Properties 屬性訪問沖突
對(duì)于值類型像 structures,tuple,enumerations 他們是由單個(gè)的值組成。任何一個(gè)屬性的修改,都會(huì)對(duì)整個(gè)值持有讀或?qū)懙脑L問。例如下面會(huì)產(chǎn)生些訪問沖突
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
同時(shí)持有 playerInfomation
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error
同時(shí)持有 holly
但是在實(shí)際開發(fā)中,有時(shí)結(jié)構(gòu)體屬性的覆蓋訪問是安全的。如下例子:
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // OK
}
上面這個(gè)例子中,oscar 是局部變量 health 和 energy 這兩個(gè)儲(chǔ)屬性沒有在任何地方交互,所以編譯器判斷這個(gè)內(nèi)存覆蓋訪問是安全的。
Memory safety 的目的是為了保證內(nèi)存訪問的安全,但是唯一訪問的要求更加的嚴(yán)格。Swift 允許非唯一訪問內(nèi)存,只要它能夠證明內(nèi)存是安全的。
對(duì)于結(jié)構(gòu)體,滿足下面條件的就可以證明是安全的
- 你正在訪問的的只有實(shí)例的存儲(chǔ)屬性,不包括計(jì)算屬性和類屬性
- 結(jié)構(gòu)體是局部變量
- 結(jié)構(gòu)體沒有被閉包捕獲,或者被非逃逸閉包捕獲。 (捕獲就是放在閉包你面,我說的哈哈)
如果編譯器不能證明訪問是安全的,它就不允許訪問,就會(huì)報(bào)錯(cuò)!