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)系作者
  • 序言:七十年代末榜田,一起剝皮案震驚了整個(gè)濱河市益兄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌箭券,老刑警劉巖净捅,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異辩块,居然都是意外死亡蛔六,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門废亭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來国章,“玉大人,你說我怎么就攤上這事豆村∫菏蓿” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵掌动,是天一觀的道長(zhǎng)四啰。 經(jīng)常有香客問我,道長(zhǎng)粗恢,這世上最難降的妖魔是什么柑晒? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮眷射,結(jié)果婚禮上匙赞,老公的妹妹穿的比我還像新娘恋追。我一直安慰自己,他們只是感情好罚屋,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗅绸,像睡著了一般脾猛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鱼鸠,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天猛拴,我揣著相機(jī)與錄音,去河邊找鬼蚀狰。 笑死愉昆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的麻蹋。 我是一名探鬼主播跛溉,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼扮授!你這毒婦竟也來了芳室?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤刹勃,失蹤者是張志新(化名)和其女友劉穎堪侯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荔仁,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伍宦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乏梁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片次洼。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掌呜,靈堂內(nèi)的尸體忽然破棺而出滓玖,到底是詐尸還是另有隱情,我是刑警寧澤质蕉,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布势篡,位于F島的核電站,受9級(jí)特大地震影響模暗,放射性物質(zhì)發(fā)生泄漏禁悠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一兑宇、第九天 我趴在偏房一處隱蔽的房頂上張望碍侦。 院中可真熱鬧,春花似錦、人聲如沸瓷产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽濒旦。三九已至株旷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尔邓,已是汗流浹背晾剖。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梯嗽,地道東北人齿尽。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像灯节,于是被迫代替她去往敵國(guó)和親循头。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

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