25、【Swift】?jī)?nèi)存安全

  • Swift 安全性
    • 使用前就初始化
    • 內(nèi)存在變量釋放后不能再訪問
    • 數(shù)組會(huì)檢查越界錯(cuò)誤
  • Swift 還通過要求標(biāo)記內(nèi)存位置來確保代碼對(duì)內(nèi)存有獨(dú)占訪問權(quán),以確保了同一內(nèi)存多訪問時(shí)不會(huì)沖突。
    • 了解一下什么情況下會(huì)潛在導(dǎo)致沖突
    • 避免寫出對(duì)內(nèi)存訪問沖突的代碼

理解內(nèi)存訪問沖突

  • 出現(xiàn)場(chǎng)景:給變量賦值,或者傳遞參數(shù)給函數(shù)
  • 比如說,下面代碼同時(shí)包含了讀取訪問和寫入訪問:
// 向 one 所在的內(nèi)存區(qū)域發(fā)起一次寫操作
var one = 1

// 向 one 所在的內(nèi)存區(qū)域發(fā)起一次讀操作
print("We're number \(one)!")
  • 添加預(yù)算項(xiàng)進(jìn)入表里的時(shí)候,它只是在一個(gè)臨時(shí)的,錯(cuò)誤的狀態(tài),因?yàn)榭倲?shù)還沒有被更新
  • 在添加數(shù)據(jù)的過程中讀取總數(shù)就會(huì)讀取到錯(cuò)誤的信息。
../_images/memory_shopping_2x.png

這里訪問沖突的討論是在單線程的情境下討論的,并沒有使用并發(fā)或者多線程。

在單線程遇到內(nèi)存訪問沖突,Swift 會(huì)保證你在要么編譯時(shí)要么運(yùn)行時(shí)得到錯(cuò)誤。

對(duì)于多線程的代碼,可以使用 Thread Sanitizer 去幫助檢測(cè)多線程的沖突

內(nèi)存訪問性質(zhì)

  • 沖突會(huì)在兩個(gè)訪問,同時(shí)滿足以下條件時(shí)發(fā)生:
    • 至少一個(gè)是寫入訪問;
    • 它們?cè)L問的是同一塊內(nèi)存;
    • 它們的訪問時(shí)間重疊。
  • 讀和寫訪問的區(qū)別
    • 寫訪問會(huì)改變存儲(chǔ)地址,而讀操作不會(huì)(存儲(chǔ)地址是指向正在訪問的東西(例如一個(gè)變量,常量或者屬性)的位置的值)
  • 內(nèi)存訪問的時(shí)長(zhǎng)要么是瞬時(shí)的,要么是長(zhǎng)期的
  • 瞬時(shí)訪問:一個(gè)訪問在啟動(dòng)后其他代碼不能執(zhí)行直到它結(jié)束后才能
  • 兩個(gè)即時(shí)訪問不能同時(shí)發(fā)生
  • 大多數(shù)內(nèi)存訪問都是即時(shí)
func oneMore(than number: Int) -> Int {
    return number + 1
}

var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// 打印“2”
  • 長(zhǎng)期訪問:會(huì)在別的代碼執(zhí)行時(shí)持續(xù)進(jìn)行
    • 長(zhǎng)期訪問,可被別的長(zhǎng)期訪問、訪問重疊
  • 重疊訪問場(chǎng)景
    • 使用 in-out 參數(shù)的函數(shù)和方法
    • 結(jié)構(gòu)體的 mutating 方法里

In-Out 參數(shù)的訪問沖突

  • 沖突本質(zhì):一個(gè)函數(shù)會(huì)對(duì)它所有的 in-out 參數(shù)進(jìn)行長(zhǎng)期訪問
  • 順序:
    • 所有非 in-out 參數(shù)處理完之后開始,直到函數(shù)執(zhí)行完畢為止
    • 有多個(gè) in-out 參數(shù),則寫訪問開始的順序與參數(shù)的順序一致
  • 不能在訪問以 in-out 形式傳入后的原變量,即使作用域原則和訪問權(quán)限允許
var stepSize = 1// 全局變量

func increment(_ number: inout Int) {
    number += stepSize //  stepSize 的讀訪問與 number 的寫訪問重疊了
}

increment(&stepSize)
// 錯(cuò)誤:stepSize 訪問沖突
  • numberstepSize 都指向了同一個(gè)存儲(chǔ)地址
  • 同一塊內(nèi)存的讀和寫訪問重疊了
image
  • 解決 inout 參數(shù)訪問沖突:拷貝一份 stepSize
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize)
 
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
// stepSize is now 2
  • 讀訪問在寫操作之前就已經(jīng)結(jié)束了,所以不會(huì)有沖突。
  • 同一個(gè)函數(shù)的多個(gè) in-out 參數(shù)里傳入同一個(gè)變量,產(chǎn)生沖突
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)  // 正常, 訪問的是不同的內(nèi)存位置
balance(&playerOneScore, &playerOneScore)// 同時(shí)訪問同一個(gè)的存儲(chǔ)地址。
// 錯(cuò)誤:playerOneScore 訪問沖突

操作符也是函數(shù),也會(huì)對(duì) in-out 參數(shù)進(jìn)行長(zhǎng)期訪問

balance(_:_:) 是一個(gè)名為 <^> 的操作符函數(shù),那么 playerOneScore <^> playerOneScore 也會(huì)造成像 balance(&playerOneScore, &playerOneScore) 一樣的沖突

方法里 self 的訪問沖突

  • 本質(zhì):結(jié)構(gòu)體的 mutating 方法會(huì)在調(diào)用期間對(duì) self 進(jìn)行訪問
struct Player {
    var name: String
    var health: Int
    var energy: Int

    static let maxHealth = 10
    mutating func restoreHealth() {
        health = Player.maxHealth
    }
}
  • 不管有沒有調(diào)用 self,只要 標(biāo)記了mutating

    • 在上面的 restoreHealth() 方法里,一個(gè)對(duì)于 self 的寫訪問會(huì)從方法開始直到方法 return
    • 不可以對(duì) Player 實(shí)例的屬性發(fā)起重疊的訪問
  • shareHealth(with:) 接受另一個(gè) Player 的實(shí)例作為 in-out 參數(shù),有訪問重疊的可能性


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)  // 正常
  • oscar 玩家的血量分享給 maria 玩家
    • 方法調(diào)用時(shí)會(huì)對(duì) oscar 發(fā)起寫訪問,在 mutating 方法里 self 就是 oscar
    • maria 也會(huì)發(fā)起寫訪問,因?yàn)?maria 作為 in-out 參數(shù)傳入
    • 訪問內(nèi)存的不同位置。即使兩個(gè)寫訪問重疊了,它們也不會(huì)沖突
img
oscar.shareHealth(with: &oscar)
// 錯(cuò)誤:oscar 訪問沖突
  • selfteammate 都指向了同一個(gè)存儲(chǔ)地址
  • 同一塊內(nèi)存同時(shí)進(jìn)行兩個(gè)寫訪問,并且它們重疊了,就此產(chǎn)生了沖突
image
oscar.shareHealth(with: &oscar)
// 錯(cuò)誤:oscar 訪問沖突
  • selfteammate 都指向了同一個(gè)存儲(chǔ)地址
  • 同一塊內(nèi)存同時(shí)進(jìn)行兩個(gè)寫訪問,并且它們重疊了,就此產(chǎn)生了沖突
image

屬性的訪問沖突

  • 出現(xiàn)場(chǎng)景:
    • 值類型:結(jié)構(gòu)體,元組和枚舉,由多個(gè)獨(dú)立的值組成
    • 修改值的一部分都是對(duì)整個(gè)值的修改
    • 一個(gè)屬性的讀或?qū)懺L問都需要訪問整一個(gè)值
  • 如,元組元素的寫訪問重疊會(huì)產(chǎn)生沖突:
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// 錯(cuò)誤:playerInformation 的屬性訪問沖突
  • 傳入同一元組的元素對(duì) balance(_:_:) 進(jìn)行調(diào)用,產(chǎn)生了沖突,因?yàn)?playerInformation 的訪問產(chǎn)生了寫訪問重疊
  • 作為 in-out 參數(shù)傳入
  • 對(duì)于元組元素的寫訪問都需要對(duì)整個(gè)元組發(fā)起寫訪問
  • 展示錯(cuò)誤:對(duì)于一個(gè)存儲(chǔ)在全局變量里的結(jié)構(gòu)體屬性的寫訪問重疊 (struct Player)
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // 錯(cuò)誤
  • 解決:將變量 holly 改為本地變量,而非全局變量,
func someFunction() {
    var oscar = Player(name: "Oscar", health: 10, energy: 10)
    balance(&oscar.health, &oscar.energy)  // 正常
}
// 兩個(gè)存儲(chǔ)屬性任何情況下都不會(huì)相互影響(全局變量,傳指針,局部變量傳值)
  • 遵循下面原則,編譯器可保證結(jié)構(gòu)體屬性的重疊訪問安全
    • 訪問的是實(shí)例的存儲(chǔ)屬性,而非計(jì)算屬性或類的屬性
    • 結(jié)構(gòu)體是本地變量的值,而非全局變量
    • 結(jié)構(gòu)體要么沒有被閉包捕獲,要么只被非逃逸閉包捕獲了
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 默認(rèn)情況下,Swift可以防止代碼中出現(xiàn)不安全行為。例如,Swift確保變量在使用之前被初始化,內(nèi)存在被釋放后不被...
    WSJay閱讀 1,757評(píng)論 1 7
  • 默認(rèn)情況下,Swift 會(huì)阻止你代碼里不安全的行為。 例如:Swift 會(huì)保證變量在使用之前就完成初始化,在內(nèi)存被...
    DevXue閱讀 396評(píng)論 0 0
  • 前言 亂譯:不是規(guī)規(guī)矩矩的翻譯,主要目的是為了學(xué)知識(shí)。但也是無奈之舉,水平有限,我不會(huì)啊。有些地方我加入了自己的理...
    jianshudxw閱讀 399評(píng)論 0 0
  • 本文主要翻譯今年 The Swift Programming Language (Swift 4) 中新出的章節(jié) ...
    tingxins閱讀 3,775評(píng)論 3 6
  • 同時(shí)讀寫一個(gè)存儲(chǔ)地址,會(huì)引發(fā)訪問沖突。 重疊訪問主要出現(xiàn)在: 使用in-out參數(shù)的函數(shù)和方法 結(jié)構(gòu)體的mutat...
    xmb閱讀 410評(píng)論 0 0

友情鏈接更多精彩內(nèi)容