Swift在什么情況會發(fā)生內(nèi)存訪問沖突詳解
前言
眾所周知,Swift 是一門類型安全的語言,它會通過編譯器報錯來阻止你代碼中不安全的行為。比如變量必須在使用之前聲明、變量被銷毀之后內(nèi)存不能在訪問、數(shù)組越界等問題。
Swift 會通過對于修改同一塊內(nèi)存,同一時間以互斥訪問權(quán)限的方式(同一時間,只能有一個寫權(quán)限),來確保你的代碼不會發(fā)生內(nèi)存訪問沖突。雖然 Swift 是自動管理內(nèi)存的,在大多數(shù)情況下你并不需要關(guān)心這個。但理解何種情況下會發(fā)生內(nèi)存訪問沖突也是十分必要的。
首先,來看一下什么是內(nèi)存訪問沖突。
內(nèi)存訪問沖突
當你設(shè)值或者讀取變量的值得時候,就會訪問內(nèi)存。
var age = 10 // 寫權(quán)限 print(age) // 讀權(quán)限
當我們對同一塊內(nèi)存,同時進行讀寫操作時,會產(chǎn)生不可預(yù)知的錯誤。比如上面的 age,假如在你讀取它值的期間有別的代碼將它設(shè)為 20,那么你讀取到的有可能是 10,也有可能是 20。這就產(chǎn)生了問題。
內(nèi)存訪問沖突:對同一塊內(nèi)存,同時進行讀寫操作,或者同時進行多個寫入操作時,就會造成內(nèi)存訪問沖突。
了解了什么是內(nèi)存訪問沖突,下面來看下什么情況下回造成內(nèi)存訪問沖突。
In-Out 參數(shù)
當 In-Out 參數(shù)為全局變量,并且該變量在函數(shù)體內(nèi)被修改時,就會造成內(nèi)存訪問沖突。比如下面的代碼:
var age = 10 func increment(_ num: inout Int) { // step1 num += age // step2 } increment(&age)
increment(:) 在整個函數(shù)體內(nèi),對所有的 In-Out 參數(shù)都有寫權(quán)限。在上述代碼中,step1 已經(jīng)獲得了 age 的寫權(quán)限,而 step2 有得到了 age 的讀權(quán)限,這樣就造成了同一塊內(nèi)存,同時進行了讀寫操作。從而造成了內(nèi)存訪問沖突。
上面的問題可以通過將 age 拷貝一份來解決:
// step1 var copyOfAge = age increment(©OfAge) age = copyOfAge
step1 將 age 的值拷貝到另一塊內(nèi)存上,這樣在函數(shù)體內(nèi)就是存在對 age 的讀權(quán)限和對 copyOfAge 的寫權(quán)限,因為 age 和 copyOfAge 是兩塊內(nèi)存,所以就不會造成內(nèi)存訪問沖突。
結(jié)構(gòu)體的 mutating 函數(shù)
對于結(jié)構(gòu)體的 mutating 函數(shù)來說,它整個函數(shù)體都有 self 的寫權(quán)限。
struct Person { var age: Int mutating func increment(_ num: inout Int) { age += num } } var p1 = Person(age: 10) p1.increment(&p1.age)
上述的代碼編譯器會報錯:Overlapping accesses to 'p1', but modification requires exclusive access; consider copying to a local variable
。很明顯這是一個內(nèi)存訪問沖突。
In-Out 參數(shù)獲得了 p1 的寫權(quán)限;mutating 函數(shù)也獲得了 p1 的寫權(quán)限。同一塊內(nèi)存,同時有兩個寫操作。造成內(nèi)存訪問沖突。可以通過同上的拷貝操作來解決。
值類型的屬性
對于結(jié)構(gòu)體、枚舉、元祖等值類型來說,修改它們的屬性就相當于修改它們整個的值。比如下面的代碼:
func increment(_ num1: inout Int, _ num2: inout Int) { print(num1 + num2) } var tuple = (age: 10, height: 20) increment(&tuple.age, &tuple.height)
&tuple.age 拿到了 tuple 的寫權(quán)限,&tuple.height 又拿了 tuple 的寫權(quán)限。同一塊內(nèi)存,同時有兩個寫操作。造成內(nèi)存訪問沖突。
這個問題可以通過局部變量來解決:
func someFunction() { var tuple = (age: 10, height: 20) increment(&tuple.age, &tuple.height) }
因為在 someFunction() 函數(shù)里,age 和 height 沒有產(chǎn)生任何的交互(沒有在其期間去讀取或者寫入 age 和 height),所以編譯器可以保證內(nèi)存安全。
PS:關(guān)于評論區(qū)的問題,在 someFunction() 函數(shù)里沒有任何交互是什么意思?
答:在someFunction() 里,編譯器可以保證沒有別的線程來讀取或者修改 tuple。因此,可以保證內(nèi)存安全。而對于全局變量,編譯器無法保證是否有別的線程在讀取或者修改。
下面的代碼就是在函數(shù)體內(nèi)有交互的代碼,雖然是局部變量,但涉及多個線程修改 tuple 的值,因此會造成內(nèi)存訪問沖突:
func someFunction() { var tuple = (age: 10, height: 20) DispatchQueue.main.async { tuple.age += 10 } DispatchQueue.main.async { increment(&tuple.age, &tuple.height) } }
總結(jié)
對同一塊內(nèi)存,同時進行讀寫操作,或者同時進行多個寫入操作時,就會造成內(nèi)存訪問沖突。
會造成內(nèi)存訪問沖突的情況:
- In-Out 為全局參數(shù),并且在函數(shù)體內(nèi)修改了它。
- 結(jié)構(gòu)體的 mutating 函數(shù)內(nèi)修改結(jié)構(gòu)體的值。
- 同一值類型的多個屬性當做函數(shù)的 In-Out 參數(shù)。
到此這篇關(guān)于Swift在什么情況會發(fā)生內(nèi)存訪問沖突的文章就介紹到這了,更多相關(guān)Swift內(nèi)存訪問沖突內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Swift 3.0基礎(chǔ)學(xué)習(xí)之下標
這篇文章主要介紹了Swift 3.0基礎(chǔ)學(xué)習(xí)之下標的相關(guān)資料,文中介紹的非常詳細,對大家學(xué)習(xí)或者使用swift具有一定的參考價值,需要的朋友下面來一起看看吧。2017-03-03Swift簡單快速的動態(tài)更換app圖標AppIcon方法示例
這篇文章主要為大家介紹了Swift動態(tài)更換app圖標AppIcon的簡單快速方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06Swift UILable 設(shè)置內(nèi)邊距實例代碼
本文主要介紹Swift UILable 設(shè)置內(nèi)邊距,這里提供示例代碼供大家參考,有需要的小伙伴可以看下2016-07-07SwiftUI使用Paths和AnimatableData實現(xiàn)酷炫的顏色切換動畫
這篇文章主要介紹了SwiftUI使用Paths和AnimatableData實現(xiàn)酷炫的顏色切換動畫,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-05-05