關於 Swift 4 中內存安全訪問

前言

本文主要翻譯今年 The Swift Programming Language (Swift 4) 中新出的章節 -《Memory Safety》。在 Swift 4 中,內存安全訪問進行很大的優化(《What's New in Swift 4 ?》)。html

默認狀況下,Swift 會克服代碼層面上的一些不安全的行爲,如:確保一個變量被初始化完後才能被訪問、確保變量在銷燬後不會被訪問等等安全操做。安全

Swift 也會確保在多路訪問內存中同一區域時不會衝突(獨佔訪問該區域)。一般狀況下,咱們徹底無需考慮內存訪問衝突的問題,由於 Swift 是自動管理內存的。然而,在碼代碼的時候,瞭解那些地方可能發生內存訪問衝突是很是重要的。一般狀況下,若是你的代碼有內存訪問衝突,那麼 Xcode 會提示編譯錯誤或者運行時錯誤。多線程

本文不會介紹什麼是內存訪問衝突。詳見 The Swift Programming Language (Swift 4)。若是你寫的是併發或者多線程的程序,內存衝突訪問與單線程是很是類似的一個問題。本文主要討論單線程上的內存衝突訪問。若是想檢測多線程是否存在內存訪問衝突,你能夠看看這篇文檔閉包

咱們能夠把訪問分爲兩種:即時和長期(instantaneous & long-term)併發

  • 即時訪問:即在訪問開始至結束前都不可能有其餘代碼來訪問同一區域。
  • 長期訪問:即在訪問開始至結束前可能有其餘代碼來訪問同一區域。長期訪問可能和其餘即時訪問或者長期訪問重疊。

重疊訪問主要帶有 in-out 參數的函數(或方法)以及結構體中帶有 mutating 關鍵字的方法。咱們下面來看看例子。app

In-Out 參數的訪問衝突

一個函數對其 in-out 參數具備長期的訪問權限,以下代碼:函數

Excerpt From: Apple Inc. "The Swift Programming Language (Swift 4).」 iBooks".優化

var stepSize = 1
 
func increment(_ number: inout Int) {
    number += stepSize
}
 
increment(&stepSize)
// Error: conflicting accesses to stepSize

在上述代碼中,stepSize 是一個全局變量,並且被做爲一個 in-out 參數傳給 increment(_:) 方法。衝突的緣由在於 numberstepSize 引用的是內存中同一區域,而且同時進行讀寫訪問,所以致使訪問衝突。ui

memory_increment_2x

咱們能夠採用複製 stepSize 的方式解決該問題:線程

// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize)
 
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
// stepSize is now 2

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)  // OK

上述代碼是 Ok 的,即時寫入權限在時間上是重疊的,可是是分別訪問 oscar 的 health 和 maria 的 health,所以在 shareHealth(with:) 方法中並無發生內存訪問衝突。

memory_share_health_maria_2x

然而,若是你把 oscar 做爲參數傳給 shareHealth(with:),那麼就會產生內存訪問衝突:

oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar

很顯然,shareHealth(with:) 方法中的 selfteammate 同時指向內存中同一區域,即同時對 oscarhealth 進行讀寫訪問,所以致使訪問衝突。

memory_share_health_oscar_2x

屬性的訪問衝突

像結構體、元組、枚舉這些類型都是由各個值組成的,如:結構體的各類屬性、元組的各類元素等等。由於它們都是值類型,這意味着對其中一個屬性的讀寫訪問就是對整個值進行讀寫訪問。代碼以下:

var playerInformation = (health: 10, energy: 20)

balance(&playerInformation.health, &playerInformation.energy)

// Error: conflicting access to properties of playerInformation

上述代碼不難理解,由於元祖是值類型,上述 balance(_:_:) 發生內存訪問衝突,即同時訪問 playerInformation。

下面咱們再看一下結構體,其中 holly 是一個全局變量

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // Error

上述代碼會報這樣一個錯誤:Simultaneous accesses to 0x10****580, but modification requires exclusive access。其實就是內存訪問衝突了,Swift 4 中也針對這塊作了優化處理,感興趣的同窗能夠查閱我以前寫的一篇文章《[WWDC17] What's New in Swift 4 ?》

在實踐中,上述代碼中的 holly 通常是個局部變量而非全局變量,編譯器能夠保證對結構體的存儲屬性進行重疊訪問是安全的,代碼以下:

func someFunction() {
    var oscar = Player(name: "Oscar", health: 10, energy: 10)
    balance(&oscar.health, &oscar.energy)  // OK
}

上述代碼運行是 Ok 的,有時候,限制結構體的各屬性進行重疊訪問是沒有必要的,這也就是爲何 someFunction() 沒有發生衝突訪問的緣由。內存訪問安全雖應當獲得保證,可是獨佔訪問比內存安全訪問要求更加嚴格,從上述代碼可看出,即時違背了獨佔訪問的原則,內存安全也能獲得保證。通常狀況下,編譯器會在以下條件下保證對結構體的存儲屬性進行安全的重疊訪問:

  • 只訪問某個實例的存儲屬性,而不是計算屬性或類屬性
  • 訪問的是局部的結構體變量,而不是全局變量
  • 結構體沒有被任何閉包所捕獲,或者僅被非逃逸閉包捕獲。

感興趣的同窗能夠查閱這裏 The Swift Programming Language (Swift 4)

相關文章
相關標籤/搜索