即使你剛剛開(kāi)始編程家凯,也可能會(huì)無(wú)意間了解到值類(lèi)型缓醋。而更有可能的是你根本沒(méi)有意識(shí)到自己在使用它們,但它們帶有一些有趣和不可不知的屬性绊诲,我將在下文中介紹它們送粱。
什么是值類(lèi)型?
你可能知道 class 是引用類(lèi)型掂之,并且可以在不同變量之間共享對(duì)象的引用抗俄。你可能還知道,可以通過(guò)引用同一個(gè)對(duì)象的多個(gè)變量來(lái)改變?cè)搶?duì)象的狀態(tài)世舰,如下所示:
class MyReferenceType {
var a: Int
init(a: Int) {
self.a = a
}
}
var R1 = MyReferenceType(a: 1)
var R2 = R1
R2.a = 42
print(R1.a, R2.a)
// 打印 "42 42"
許多不同的變量能夠改變同一個(gè)對(duì)象的事實(shí)有時(shí)被稱(chēng)為“別名問(wèn)題”动雹,如果你不小心的話(huà),它可能產(chǎn)生一些非常難以跟蹤的錯(cuò)誤跟压。
另一方面胰蝠, 值類(lèi)型的行為更像我們期望的標(biāo)準(zhǔn)基礎(chǔ)類(lèi)型的行為。這意味著你期望 int
, double
和 float
所做所有的事情茸塞,值類(lèi)型也都能做到躲庄。比如說(shuō)在執(zhí)行下面的操作時(shí),你會(huì)期望變量 b
擁有自己可修改的數(shù)字(42)的副本钾虐,而且當(dāng)它改變時(shí)變量 a
不受影響:
var a: Int = 42
var b: Int = a
b = 0
print(a, b)
// 打印 "42 0"
這種語(yǔ)義也擴(kuò)展到了其它值類(lèi)型噪窘,在 Swift 中 struct
是值類(lèi)型,enum
和 tuple
也是效扫。這就是說(shuō)只要為變量被賦值給另一個(gè)變量倔监,就會(huì)分配一個(gè)新對(duì)象。下面的代碼中荡短,修改 v2
不會(huì)影響 v1
的值:
struct MyValueType {
var a: Int
}
var V1 = MyValueType(a: 1)
var V2 = V1
V2.a = 42
print(V1)
// 打印 "MyValueType(a: 1)"
print(V2)
// 打印 "MyValueType(a: 42)"
繼續(xù)深入……
正如在上一節(jié)所看到的那樣丐枉,引用類(lèi)型和值類(lèi)型在一些非常容易理解的基本方式上有所不同。但也有一些時(shí)候掘托,它們之間的界限變得模糊了瘦锹,比如說(shuō) Array (它是個(gè)struct
)。我們將借助一個(gè)工具方法闪盔,它使我們能夠比較內(nèi)存地址弯院,以便更深入地了解所發(fā)生的情況:
func address(of pointer: UnsafeRawPointer) -> String {
let length = 2 + 2 * MemoryLayout<UnsafeRawPointer>.size
let address = Int(bitPattern: pointer)
return String(format: "%0\(length)p", address)
}
var V1 = [Int]()
var V2 = V1
print(address(of: &V1) == address(of: &V2))
// 打印 "true"
V1 = [Int]()
V2 = [Int]()
print(address(of: &V1) == address(of: &V2))
// 打印 "true"
什么情況?即使我們的數(shù)組是個(gè)值類(lèi)型泪掀,當(dāng)它被賦值給變量 v2
時(shí)听绳,也不會(huì)復(fù)制到新的內(nèi)存地址。即使我們?yōu)槊總€(gè)變量創(chuàng)建并分配數(shù)字實(shí)例异赫,它們也不會(huì)落在不同的內(nèi)存地址上椅挣,到底怎么回事?
Swift 編譯器很聰明塔拳,它會(huì)做一大通優(yōu)化鼠证,以確保程序消耗盡可能少的資源。在上面的實(shí)例中靠抑,編譯器注意到了這兩個(gè)變量指向兩個(gè)相等的數(shù)據(jù)量九,由于它們?cè)趫?zhí)行期間沒(méi)有改變,可以通過(guò)分配一個(gè)實(shí)例然后將我們的變量指向同一個(gè)實(shí)例來(lái)節(jié)省一些內(nèi)存和執(zhí)行時(shí)間颂碧。這種共享后資源無(wú)任何改變的行為被稱(chēng)作“寫(xiě)時(shí)復(fù)制(Copy-On-Write)”荠列。
寫(xiě)時(shí)復(fù)制機(jī)制
Swift 中一大堆值類(lèi)型都實(shí)現(xiàn)了寫(xiě)時(shí)復(fù)制(COW)行為,這就是說(shuō)只要不發(fā)生修改载城,多個(gè)變量會(huì)指向相同的內(nèi)存位置肌似。如果某個(gè)變量想要改變數(shù)據(jù),它將被分配新的內(nèi)存并復(fù)制內(nèi)容诉瓦,然后在新內(nèi)存地址上調(diào)整結(jié)構(gòu)锈嫩。實(shí)際上受楼,持有 COW 值的變量不像非 COW 變量那樣對(duì)該值享有“專(zhuān)有權(quán)”。在我們的例子調(diào)用 address
方法顯示 v2
申請(qǐng)了它自己的內(nèi)存并將 v1
的值拷貝了過(guò)去呼寸,在某種意義上第一個(gè) MyValueType 對(duì)象將為 v1
所專(zhuān)有艳汽。
為了證明分配初始值的變量不一定是保留它的變量,我將貼一張 XCode Playground 的截圖对雪『雍看一下里面打印的內(nèi)存地址,可以發(fā)現(xiàn)由于第一個(gè)變量想要在共享之后改變底層的值瑟捣,不得不放棄初始化時(shí)分配的地址和對(duì)象馋艺,改而分配新的內(nèi)存:
以上就是所有要介紹的內(nèi)容,如果你想了解有關(guān)值類(lèi)型的更多信息以及它可能為你的項(xiàng)目帶來(lái)哪些優(yōu)勢(shì)迈套,建議觀看 WWDC 2015 上蘋(píng)果的演示文稿捐祠。
原文:https://medium.com/@JimmyMAndersson/understanding-swift-value-semantics-d84d57b937a2
作者:Jimmy M Andersson
編譯:碼王爺