Swift文件Chapter 24 記憶體安全
阿新 • • 發佈:2022-01-24
理解記憶體訪問衝突
記憶體訪問的衝突會發生在你的程式碼嘗試同時訪問同一個儲存地址的時侯。同一個儲存地址的多個訪問同時發生會造成不可預計或不一致的行為。
記憶體訪問性質
記憶體訪問衝突時,要考慮記憶體訪問上下文中的這三個性質:訪問是讀還是寫,訪問的時長,以及被訪問的儲存地址。特別是,衝突會發生在當你有兩個訪問符合下列的情況:
- 至少有一個是寫訪問;
- 它們訪問的是同一個儲存地址;
- 它們的訪問在時間線上部分重疊。
如果一個訪問不可能在其訪問期間被其它程式碼訪問,那麼就是一個瞬時訪問。大多數記憶體訪問都是瞬時的。瞬時訪問和長期訪問的區別在於別的程式碼有沒有可能在訪問期間同時訪問,也就是在時間線上的重疊。一個長期訪問
In-Out引數的訪問衝突
一個函式會對它所有的in-out引數進行長期寫訪問。in-out引數的寫訪問會在所有非in-out引數處理完之後開始,直到函式執行完畢為止。
長期訪問的存在會造成一個結果,你不能在訪問以in-out形式傳入後的原變數,即使作用域原則和訪問許可權允許——任何訪問原變數的行為都會造成衝突。
var stepSize = 1 func increment(_ number: inout Int) { number += stepSize } increment(&stepSize) // 錯誤:stepSize 訪問衝突
長期寫訪問的存在還會造成另一種結果,往同一個函式的多個in-out引數裡傳入同一個變數也會產生衝突:
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) // 正常 balance(&playerOneScore, &playerOneScore) // 錯誤:playerOneScore 訪問衝突
方法裡self的訪問衝突
一個結構體的mutating
方法會在呼叫期間對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) // 正常
如果和Maria分享血量,那麼不會出現問題。
但是如果自己和自己分享血量,就會產生衝突。
同時寫到了同一個地方。
屬性的訪問衝突
結構體,元組和列舉的型別都是由多個獨立的值組成的,例如結構體的屬性或元組的元素。因為它們都是值型別,修改值的任何一部分都是對於整個值的修改,意味著其中一個屬性的讀或寫訪問都需要訪問整一個值。
在實踐中,大多數對於結構體屬性的訪問都會安全的重疊。遵循下面的原則時,它可以保證結構體屬性的重疊訪問是安全的:
- 你訪問的是例項的儲存屬性,而不是計算屬性或類的屬性;
- 結構體是本地變數的值,而非全域性變數;
- 結構體要麼沒有被閉包捕獲,要麼只被非逃逸閉包捕獲了。