Swift集合(Collection)類型的賦值和拷貝行爲
集合(Collection)類型的賦值和拷貝行爲
Swift 中數組(Array)
和字典(Dictionary)
類型均以結構體的形式實現。然而當數組被賦予一個常量或變量,或被傳遞給一個函數或方法時,其拷貝行爲與字典和其它結構體有些許不同。
以下對數組
和結構體
的行爲描述與對NSArray
和NSDictionary
的行爲描述在本質上不同,後者是以類的形式實現,前者是以結構體的形式實現。NSArray
和NSDictionary
實例總是以對已有實例引用,而不是拷貝的方式被賦值和傳遞。
注意:
以下是對於數組,字典,字符串和其它值的拷貝
的描述。 在你的代碼中,拷貝好像是確實是在有拷貝行爲的地方產生過。然而,在 Swift 的後臺中,只有確有必要,實際(actual)
拷貝纔會被執行。Swift 管理所有的值拷貝以確保性能最優化的性能,所以你也沒有必要去避免賦值以保證最優性能。(實際賦值由系統管理優化)
字典類型的賦值和拷貝行爲
無論何時將一個字典
實例賦給一個常量或變量,或者傳遞給一個函數或方法,這個字典會即會在賦值或調用發生時被拷貝。在章節結構體和枚舉是值類型中將會對此過程進行詳細介紹。
如果字典
實例中所儲存的鍵(keys)和/或值(values)是值類型(結構體或枚舉),當賦值或調用發生時,它們都會被拷貝。相反,如果鍵(keys)和/或值(values)是引用類型,被拷貝的將會是引用,而不是被它們引用的類實例或函數。字典
的鍵和值的拷貝行爲與結構體所儲存的屬性的拷貝行爲相同。
下面的示例定義了一個名爲ages
的字典,其中儲存了四個人的名字和年齡。ages
字典被賦予了一個名爲copiedAges
的新變量,同時ages
在賦值的過程中被拷貝。賦值結束後,ages
和copiedAges
成爲兩個相互獨立的字典。
var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
var copiedAges = ages
這個字典的鍵(keys)是字符串(String)
類型,值(values)是整(Int)
類型。這兩種類型在Swift 中都是值類型(value types),所以當字典被拷貝時,兩者都會被拷貝。
我們可以通過改變一個字典中的年齡值(age value),檢查另一個字典中所對應的值,來證明ages
字典確實是被拷貝了。如果在copiedAges
字典中將Peter
的值設爲24
,那麼ages
字典仍然會返回修改前的值23
:
copiedAges["Peter"] = 24
println(ages["Peter"])
// 輸出 "23"
數組的賦值和拷貝行爲
在Swift 中,數組(Arrays)
類型的賦值和拷貝行爲要比字典(Dictionary)
類型的複雜的多。當操作數組內容時,數組(Array)
能提供接近C語言的的性能,並且拷貝行爲只有在必要時纔會發生。
如果你將一個數組(Array)
實例賦給一個變量或常量,或者將其作爲參數傳遞給函數或方法調用,在事件發生時數組的內容不
會被拷貝。相反,數組公用相同的元素序列。當你在一個數組內修改某一元素,修改結果也會在另一數組顯示。
對數組來說,拷貝行爲僅僅當操作有可能修改數組長度
時纔會發生。這種行爲包括了附加(appending),插入(inserting),刪除(removing)或者使用範圍下標(ranged subscript)去替換這一範圍內的元素。只有當數組拷貝確要發生時,數組內容的行爲規則與字典中鍵值的相同,參見章節[集合(collection)類型的賦值與複製行爲](#assignment_and_copy_behavior_for_collection_types。
下面的示例將一個整數(Int)
數組賦給了一個名爲a
的變量,繼而又被賦給了變量b
和c
:
var a = [1, 2, 3]
var b = a
var c = a
我們可以在a
,b
,c
上使用下標語法以得到數組的第一個元素:
println(a[0])
// 1
println(b[0])
// 1
println(c[0])
// 1
如果通過下標語法修改數組中某一元素的值,那麼a
,b
,c
中的相應值都會發生改變。請注意當你用下標語法修改某一值時,並沒有拷貝行爲伴隨發生,因爲下表語法修改值時沒有改變數組長度的可能:
a[0] = 42
println(a[0])
// 42
println(b[0])
// 42
println(c[0])
// 42
然而,當你給a
附加新元素時,數組的長度會
改變。 當附加元素這一事件發生時,Swift 會創建這個數組的一個拷貝。從此以後,a
將會是原數組的一個獨立拷貝。
拷貝發生後,如果再修改a
中元素值的話,a
將會返回與b
,c
不同的結果,因爲後兩者引用的是原來的數組:
a.append(4)
a[0] = 777
println(a[0])
// 777
println(b[0])
// 42
println(c[0])
// 42
確保數組的唯一性
在操作一個數組,或將其傳遞給函數以及方法調用之前是很有必要先確定這個數組是有一個唯一拷貝的。通過在數組變量上調用unshare
方法來確定數組引用的唯一性。(當數組賦給常量時,不能調用unshare
方法)
如果一個數組被多個變量引用,在其中的一個變量上調用unshare
方法,則會拷貝此數組,此時這個變量將會有屬於它自己的獨立數組拷貝。當數組僅被一個變量引用時,則不會有拷貝發生。
在上一個示例的最後,b
和c
都引用了同一個數組。此時在b
上調用unshare
方法則會將b
變成一個唯一個拷貝:
b.unshare()
在unshare
方法調用後再修改b
中第一個元素的值,這三個數組(a
,b
,c
)會返回不同的三個值:
b[0] = -105
println(a[0])
// 77
println(b[0])
// -105
println(c[0])
// 42
判定兩個數組是否共用相同元素
我們通過使用恆等運算符(identity operators)( === 和 !==)來判定兩個數組或子數組共用相同的儲存空間或元素。
下面這個示例使用了「等同(identical to)」 運算符(===) 來判定b
和c
是否共用相同的數組元素:
if b === c {
println("b and c still share the same array elements.")
} else {
println("b and c now refer to two independent sets of array elements.")
}
// 輸出 "b and c now refer totwo independent sets of array elements."
此外,我們還可以使用恆等運算符來判定兩個子數組是否共用相同的元素。下面這個示例中,比較了b
的兩個相等的子數組,並且確定了這兩個子數組都引用相同的元素:
if b[0...1] === b[0...1] {
println("These two subarrays share the same elements.")
} else {
println("These two subarrays do not share the same elements.")
}
// 輸出 "These two subarrays share the same elements."
強制複製數組
我們通過調用數組的copy
方法進行強制顯性複製。這個方法對數組進行了淺拷貝(shallow copy),並且返回一個包含此拷貝的新數組。
下面這個示例中定義了一個names
數組,其包含了七個人名。還定義了一個copiedNames
變量,用以儲存在names
上調用copy
方法所返回的結果:
var names = ["Mohsen", "Hilary", "Justyn", "Amy", "Rich", "Graham", "Vic"]
var copiedNames = names.copy()
我們可以通過修改一個數組中某元素,並且檢查另一個數組中對應元素的方法來判定names
數組確已被複制。如果你將copiedNames
中第一個元素從"Mohsen
"修改爲"Mo
",則names
數組返回的仍是拷貝發生前的"Mohsen
":
copiedName[0] = "Mo"
println(name[0])
// 輸出 "Mohsen"
注意:
如果你僅需要確保你對數組的引用是唯一引用,請調用unshare
方法,而不是copy
方法。unshare
方法僅會在確有必要時纔會創建數組拷貝。copy
方法會在任何時候都創建一個新的拷貝,即使引用已經是唯一引用。