網(wǎng)站建設(shè)的好處和目的網(wǎng)站關(guān)鍵詞快速排名技術(shù)
內(nèi)存的訪問(wèn),發(fā)生在給變量賦值的時(shí)候,或者傳遞值(給函數(shù))的時(shí)候,例如
var one = 1//向one的內(nèi)存區(qū)域發(fā)起一次寫(xiě)的操作
print("\(one)")//向one的內(nèi)存區(qū)域發(fā)起一次讀的操作
在 Swift 里,有很多修改值的行為都會(huì)持續(xù)好幾行代碼,在修改值的過(guò)程中進(jìn)行訪問(wèn)是有可能發(fā)生的。讀和寫(xiě)訪問(wèn)的區(qū)別很明顯:一個(gè)寫(xiě)訪問(wèn)會(huì)改變存儲(chǔ)地址,而讀操作不會(huì)。存儲(chǔ)地址是指向正在訪問(wèn)的東西(例如一個(gè)變量,常量或者屬性)的位置的值 。內(nèi)存訪問(wèn)的時(shí)長(zhǎng)要么是瞬時(shí)的,要么是長(zhǎng)期的。如果一個(gè)訪問(wèn)不可能在其訪問(wèn)期間被其它代碼訪問(wèn),那么就是一個(gè)瞬時(shí)訪問(wèn)。正常來(lái)說(shuō),兩個(gè)瞬時(shí)訪問(wèn)是不可能同時(shí)發(fā)生的。大多數(shù)內(nèi)存訪問(wèn)都是瞬時(shí)的。例如,下面列舉的所有讀和寫(xiě)訪問(wèn)都是瞬時(shí)的:
func oneMore(than number: Int) -> Int {return number + 1
}var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// 打印“2”
然而,有幾種被稱為長(zhǎng)期訪問(wèn)的內(nèi)存訪問(wèn)方式,會(huì)在別的代碼執(zhí)行時(shí)持續(xù)進(jìn)行。瞬時(shí)訪問(wèn)和長(zhǎng)期訪問(wèn)的區(qū)別在于別的代碼有沒(méi)有可能在訪問(wèn)期間同時(shí)訪問(wèn),也就是在時(shí)間線上的重疊。一個(gè)長(zhǎng)期訪問(wèn)可以被別的長(zhǎng)期訪問(wèn)或瞬時(shí)訪問(wèn)重疊。如果發(fā)生了重疊訪問(wèn),就可能會(huì)造成內(nèi)存沖突,因?yàn)樵谕粔K內(nèi)存區(qū)域同時(shí)發(fā)生讀和寫(xiě)的操作是肯定不被允許的。
重疊的訪問(wèn)主要出現(xiàn)在使用 in-out 參數(shù)的函數(shù)和方法或者結(jié)構(gòu)體的 mutating 方法里。
In-Out 參數(shù)的訪問(wèn)沖突
一個(gè)函數(shù)會(huì)對(duì)它所有的 in-out 參數(shù)保持長(zhǎng)期寫(xiě)訪問(wèn)。in-out 參數(shù)的寫(xiě)訪問(wèn)會(huì)在所有非 in-out 參數(shù)處理完之后開(kāi)始,直到函數(shù)執(zhí)行完畢為止。如果有多個(gè) in-out 參數(shù),則寫(xiě)訪問(wèn)開(kāi)始的順序與參數(shù)的順序一致。
這種長(zhǎng)期保持的寫(xiě)訪問(wèn)帶來(lái)的問(wèn)題是,你不能再訪問(wèn)以 in-out 形式傳入的原始變量,即使作用域原則和訪問(wèn)權(quán)限允許——任何訪問(wèn)原始變量的行為都會(huì)造成沖突。例如:
var stepSize = 1func increment(_ number: inout Int) {number += stepSizeprint(number)//不會(huì)產(chǎn)生讀訪問(wèn)
}increment(&stepSize)
// 錯(cuò)誤:stepSize 訪問(wèn)沖突
代碼中stepSize是一個(gè)全局變量,并且我們以inout的形式將該變量傳入了increment函數(shù)當(dāng)中,這里我們得知道的是,一個(gè)函數(shù)會(huì)對(duì)它所有的 in-out 參數(shù)保持長(zhǎng)期寫(xiě)訪問(wèn),inout參數(shù)的寫(xiě)訪問(wèn)會(huì)在所有非 in-out 參數(shù)處理完之后開(kāi)始,直到函數(shù)執(zhí)行完畢為止。這種方法的帶來(lái)的弊端就是,你不能在訪問(wèn)以inout形式傳入的原始變量(我都得到地址了,我還訪問(wèn)原始干嘛,對(duì)吧),即使作用域原則和訪問(wèn)權(quán)限允許——任何訪問(wèn)原始變量的行為都會(huì)造成沖突。上述代碼中,由于inout帶來(lái)的長(zhǎng)期寫(xiě)訪問(wèn),而我們也對(duì)stepSize進(jìn)行了讀訪問(wèn)。所以就會(huì)造成內(nèi)存沖突。(當(dāng)然,同一函數(shù)傳入兩個(gè)inout相同的數(shù)據(jù),也會(huì)造成寫(xiě)訪問(wèn)沖突)但是為什么print(number)不回發(fā)生內(nèi)存沖突呢?實(shí)際上,將inout傳入進(jìn)去時(shí),swift發(fā)生了以下操作
1. 寫(xiě)訪問(wèn)發(fā)生在調(diào)用期間:? 變量 stepSize 被作為 inout 參數(shù)傳遞給 increment。? Swift 會(huì)暫時(shí)暫停對(duì)原始變量的所有訪問(wèn),并將其訪問(wèn)權(quán)轉(zhuǎn)移給函數(shù)的 inout 參數(shù) number。2. 對(duì)原變量的隔離:? 在函數(shù)調(diào)用期間,stepSize 被隔離,外部無(wú)法訪問(wèn)。? 任何對(duì) number 的讀寫(xiě)操作都會(huì)作用于 stepSize 的值,但此時(shí)只通過(guò) number 訪問(wèn)變量。3. 生命周期的非交疊:? 函數(shù)調(diào)用開(kāi)始時(shí),stepSize 的值被寫(xiě)入到 number 的內(nèi)存。? 在函數(shù)調(diào)用結(jié)束時(shí),number 的值被寫(xiě)回到 stepSize。? 在調(diào)用期間,stepSize 不再直接可用,因此不存在交疊訪問(wèn)。
解決沖突的方式很多,這里舉例一種很雞肋的方式:
// 顯式拷貝
var copyOfStepSize = stepSize
increment(©OfStepSize)// 更新原來(lái)的值
stepSize = copyOfStepSize
// stepSize 現(xiàn)在的值是 2
方法里self帶來(lái)訪問(wèn)沖突
廢話少說(shuō),上圖
struct Player {var name: Stringvar health: Intvar energy: Intstatic let maxHealth = 10mutating func restoreHealth() {health = Player.maxHealth}
}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.shareHealth(with: &Oscar) // 錯(cuò)誤:oscar 訪問(wèn)沖突
在上述代碼中我們?cè)趐lyer結(jié)構(gòu)題方法中對(duì)restoreHealth使用mutating訪問(wèn)權(quán)限,這樣就會(huì)一直有對(duì)self的寫(xiě)訪問(wèn),而在oscar.shareHealth我們傳入了oscar,shareHealth中的inout又對(duì)Player進(jìn)行的寫(xiě)訪問(wèn),就造成了兩個(gè)寫(xiě)訪問(wèn)沖突。所以會(huì)造成內(nèi)存
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // 錯(cuò)誤
崩潰。有人疑惑為什么上面的restoreHealth()中不會(huì)發(fā)生內(nèi)存沖突呢?
我們可以看到,maxHealth我們是是用static靜態(tài)修飾的,他屬于類(lèi)型本身,而不屬于任何實(shí)例,他儲(chǔ)單獨(dú)存儲(chǔ)在靜態(tài)區(qū)域。所以訪問(wèn)自然與實(shí)例無(wú)關(guān)了。
屬性的訪問(wèn)沖突
如結(jié)構(gòu)體,元組和枚舉的類(lèi)型都是由多個(gè)獨(dú)立的值組成的,例如結(jié)構(gòu)體的屬性或元組的元素。因?yàn)樗鼈兌际侵殿?lèi)型,修改值的任何一部分都是對(duì)于整個(gè)值的修改,意味著其中一個(gè)屬性的讀或?qū)懺L問(wèn)都需要訪問(wèn)一整個(gè)值。例如,元組元素的寫(xiě)訪問(wèn)重疊會(huì)產(chǎn)生沖突:
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// 錯(cuò)誤:playerInformation 的屬性訪問(wèn)沖突
在結(jié)構(gòu)體中,下面展示了對(duì)于一個(gè)存儲(chǔ)在全局變量里的結(jié)構(gòu)體屬性的寫(xiě)訪問(wèn)重疊了。
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // 錯(cuò)誤
但是對(duì)于本地變量,編譯器就可以,編譯器就可以保證重疊訪問(wèn)是安全的(不懂但尊重)。
func someFunction() {var oscar = Player(name: "Oscar", health: 10, energy: 10)balance(&oscar.health, &oscar.energy) // 正常
}
以下就是一些規(guī)則,它可以保證結(jié)構(gòu)體屬性的重疊訪問(wèn)是安全的:
·你訪問(wèn)的是實(shí)例的存儲(chǔ)屬性,而不是計(jì)算屬性或類(lèi)的屬性
·結(jié)構(gòu)體是本地變量的值,而非全局變量
·結(jié)構(gòu)體要么沒(méi)有被閉包捕獲,要么只被非逃逸閉包捕獲了