簡(jiǎn)介
默認(rèn)情況下,Swift可以防止代碼中發(fā)生不安全的行為.例如截驮,Swift確保變量在使用之前進(jìn)行初始化亏狰,在取消分配后不訪問內(nèi)存甩恼,并檢查數(shù)組索引是否存在越界錯(cuò)誤定枷。
Swift還確保對(duì)同一內(nèi)存區(qū)域的多次訪問不會(huì)發(fā)生沖突孤澎,因?yàn)樾枰薷膬?nèi)存中某個(gè)位置的代碼才能對(duì)該內(nèi)存進(jìn)行獨(dú)占訪問。因?yàn)?strong>Swift自動(dòng)管理內(nèi)存欠窒,所以大多數(shù)時(shí)候你根本不需要考慮訪問內(nèi)存覆旭。但是,了解潛在沖突可能發(fā)生的位置非常重要,這樣我們就可以避免編寫對(duì)內(nèi)存具有沖突訪問權(quán)限的代碼姐扮。如果你的代碼確實(shí)包含沖突絮供,那么你將收到編譯時(shí)或運(yùn)行時(shí)錯(cuò)誤。
內(nèi)存訪問沖突
當(dāng)我們?cè)O(shè)置變量的值或?qū)?shù)傳遞給函數(shù)的操作時(shí),會(huì)在代碼中訪問內(nèi)存.
// A write access to the memory where one is stored.
var one = 1
// A read access from the memory where one is stored.
print("We're number \(one)!")
當(dāng)代碼的不同部分試圖同時(shí)訪問內(nèi)存中的相同位置時(shí)茶敏,可能會(huì)發(fā)生沖突的內(nèi)存訪問。同時(shí)多次訪問內(nèi)存中的某個(gè)位置會(huì)產(chǎn)生不可預(yù)測(cè)或不一致的行為.在Swift中缚俏,有一些方法可以修改跨越多行代碼的值惊搏,從而可以嘗試在自己修改的過程中訪問一個(gè)值。
如果您編寫了并發(fā)或多線程代碼忧换,則對(duì)內(nèi)存的沖突訪問可能是一個(gè)熟悉的問題恬惯。但是,此處討論的沖突訪問可能發(fā)生在單個(gè)線程上亚茬,并且不涉及并發(fā)或多線程代碼酪耳。
特征
-至少一個(gè)寫操作
-訪問內(nèi)存中的相同位置
-他們的持續(xù)時(shí)間重疊
讀寫訪問之間的區(qū)別:寫訪問權(quán)限會(huì)更改內(nèi)存中的位置,但讀訪問權(quán)限則不會(huì).內(nèi)存中的位置指的是被訪問的內(nèi)容.例如刹缝,變量碗暗,常量或?qū)傩浴4鎯?chǔ)器訪問的持續(xù)時(shí)間是瞬時(shí)的或長(zhǎng)期的梢夯。
如果在訪問開始之后但在結(jié)束之前其他代碼無法運(yùn)行言疗,則訪問是即時(shí)的
函數(shù)具有對(duì)其所有輸入輸出參數(shù)的長(zhǎng)期寫訪問權(quán)。
例如颂砸,下面代碼清單中的所有讀寫訪問都是即時(shí)的
func oneMore(than number: Int) -> Int {
return number + 1
}
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// Prints "2"
但是噪奄,有幾種方法可以訪問內(nèi)存,稱為長(zhǎng)期訪問人乓,跨越其他代碼的執(zhí)行.即時(shí)訪問和長(zhǎng)期訪問之間的區(qū)別在于勤篮,其他代碼可以在長(zhǎng)期訪問開始之后但在結(jié)束之前運(yùn)行,這稱為重疊色罚。長(zhǎng)期訪問可以與其他長(zhǎng)期訪問和即時(shí)訪問重疊碰缔。
重疊訪問主要出現(xiàn)在使用函數(shù)和方法中的
in-out
修飾的參數(shù)或mutating
修飾的方法代碼中。使用長(zhǎng)期訪問的特定Swift代碼類型將在下面的部分中討論保屯。
in-out
in-out
是修飾函數(shù)參數(shù)類型,表示該參數(shù)在函數(shù)內(nèi)修改后(即函數(shù)返回后),其值為修改后的值.
1,適用類型為變量
2,in-out
修飾后的參數(shù),在傳參時(shí)需&
修飾
in-out 訪問沖突
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// Error: conflicting accesses to stepSize
解決
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(©OfStepSize)
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
方法內(nèi)訪問沖突
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
在上面的 restoreHealth()
方法中,對(duì) self
的寫訪問權(quán)限從方法的開頭開始手负,一直持續(xù)到方法返回為止.在這種情況下, restoreHealth()
中沒有其他代碼可以重疊訪問Player實(shí)例的屬性姑尺。下面的 shareHealth(with :)
方法將另一個(gè) Player
實(shí)例作為 in-out
參數(shù)竟终,從而創(chuàng)造了重疊訪問的可能性。
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
的玩家調(diào)用shareHealth(with :)
方法與Maria
的玩家分享健康狀況不會(huì)引起沖突.在方法調(diào)用期間有一個(gè)對(duì)oscar
的寫訪問權(quán)统捶,因?yàn)閛scar是變異方法中self
的值,并且在相同的持續(xù)時(shí)間內(nèi)對(duì)maria進(jìn)行寫訪問,因?yàn)閙aria作為in-out參數(shù)傳遞喘鸟。
但是匆绣,如果您將
oscar
作為參數(shù)傳遞給shareHealth(with:)
,則存在沖突:
oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar
屬性訪問沖突
結(jié)構(gòu)什黑,元組和枚舉等類型由單個(gè)組成值組成崎淳,例如結(jié)構(gòu)的屬性或元組的元素.因?yàn)檫@些是值類型,所以改變值的任何部分都會(huì)改變整個(gè)值愕把,這意味著對(duì)其中一個(gè)屬性的讀或?qū)懺L問需要對(duì)整個(gè)值的讀或?qū)懺L問權(quán)限
例如拣凹,重疊對(duì)元組元素的寫訪問會(huì)產(chǎn)生沖突:
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error
實(shí)際上,大多數(shù)對(duì)結(jié)構(gòu)屬性的訪問都可以安全地重疊恨豁。
例如嚣镜,如果上例中的變量holly更改為局部變量而不是全局變量,則編譯器可以證明對(duì)結(jié)構(gòu)的存儲(chǔ)屬性的重疊訪問是安全的:
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // OK
}
在上面的例子中橘蜜,Oscar的health和energy 作為balance(: :)輸入?yún)?shù)菊匿。編譯器可以證明保留了內(nèi)存安全性,因?yàn)閮蓚€(gè)存儲(chǔ)的屬性不會(huì)以任何方式進(jìn)行交互计福。
為了保持存儲(chǔ)器安全性跌捆,并不總是必須限制對(duì)結(jié)構(gòu)屬性的重疊訪問.內(nèi)存安全是理想的保證,但獨(dú)占訪問是比內(nèi)存安全更嚴(yán)格的要求 - 這意味著一些代碼可以保持內(nèi)存安全棒搜,即使它違反了對(duì)內(nèi)存的獨(dú)占訪問權(quán)限.如果編譯器能夠證明對(duì)內(nèi)存的非獨(dú)占訪問仍然是安全的疹蛉,那么Swift允許這種內(nèi)存安全的代碼。具體而言力麸,如果滿足以下條件可款,則可以證明對(duì)結(jié)構(gòu)屬性的重疊訪問是安全的:
只訪問實(shí)例的存儲(chǔ)屬性,而不是計(jì)算屬性或類屬性克蚂。
結(jié)構(gòu)是局部變量的值闺鲸,而不是全局變量黎棠。
該結(jié)構(gòu)要么不被任何閉包捕獲肥橙,要么僅由non-escaping
捕獲。