第三章——集合(陣列與可變性)

bestswifter發表於2017-12-27

我們在swift中最常用的集合型別就是陣列了。陣列,簡單來說,就是一列東西。比如我們可以這樣建立一個數字的陣列:

let fibs = [0, 1, 1, 2, 3, 5]
複製程式碼

陣列有一些常用操作,比如isEmptycout方法1,分別獲取陣列的第一個和最後一個元素(除非陣列為空)。陣列還允許使用下標指令碼(subscript),通過指定一個下標直接訪問元素。但使用下標指令碼是不安全的操作,因為在獲取元素之前,你需要確保下標沒有越界,否則程式就會崩潰。

如果我們想要修改剛剛定義的陣列(比如使用apppend方法),我們會得到一個編譯錯誤。這是因為通過let關鍵字,陣列被定義為了常量(constant)。在很多情況下這麼做毫無問題,因為它可以防止你偶然間修改到這個陣列。我們需要使用var關鍵字來讓這個陣列成為一個變數(variable)。

var mutableFibs = [0, 1, 1, 2, 3, 5]
複製程式碼

這樣就容易在陣列後追加一個或一組元素了:

mutableFibs.append(8)
mutableFibs.appendContentsOf([13,21])
複製程式碼

varlet區別開來有很多好處。被定義為let的變數不能被改變,因此它更容易理解。當你讀到類似於let fibs = ……的時候,你就知道編譯器會保證fibs的值永遠不會變。這極大的幫助我們通讀整篇程式碼。需要注意的是當變數表示一個物件時,它的值是一個指標。用let來定義一個物件可以保證這個指標所指向的記憶體地址是不變的,但是它指向的記憶體中的資料是可能會改變的(也就是說例項變數)2。我們會在第四章中更加深入的理解結構體和類的區別。

Swift中的陣列有自己的值語義。具體說來就是陣列中的值從來不會共享。當建立一個新的陣列變數,然後把舊的陣列賦值給它時,其實是把舊的陣列變數複製了一份,把陣列作為引數傳給函式中也是如此。比如在下面的程式碼段中,x不會被修改。

var x = [1,2,3]
var y = x
y.append(4)
複製程式碼

var y = x這句程式碼把x複製了一份,所以在y的末尾追加一個4不會對x有任何影響,x的值依然是[1,2,3]

對比一下NSArray採用的實現可變性的方法,NSArray沒有變異方法(mutating method),需要用NSMutableArray來讓一個陣列變成可變的。

但只是有一個不可變的NSArray引用並不意味著陣列就不能在你眼皮子底下被改變。

let a = NSMutableArray(array: [1,2,3])

// 我不想讓b能被改變
let b: NSArray = a

// 但其實b還是可以被改變 —— 通過a
a.insertObject(4, atIndex:3)

// 現在b也含有4了
print(b)
複製程式碼

正確的做法是在建立b的時候把a拷貝一份:

let a = NSMutableArray(array: [1,2,3])

// 我不想讓b能被改變
let b: NSArray = a.copy() as! NSArray

a.insertObject(4, atIndex:3)
print(b)  // b現在依然還是[1,2,3]

複製程式碼

在上面的例子中,我們顯然需要把a複製一份,因為畢竟a自己是可變的。雖然在方法間傳遞陣列的情況不太常見、但這會導致很多不必要的複製。

在Swift中,我們不需要NSArrayNSMutableArray這兩種陣列了,只需要一個Swift自己的陣列便足矣。陣列的可變性由varlet關鍵字來決定。與OC不同的是,對某一個陣列的引用是不會共享的,也就是當你用let來定義第二個陣列的時候,你可以確保這第二個陣列是不會改變的。

確實,經常複製陣列會帶來效能上的問題,但實際上,Swift標註庫裡所有的集合型別在實現時都採用了“寫時複製(copy-on-write)”技術以確保資料只有在真正需要的時候才會被複制。所以在之前的例子中,xyy.append(4)呼叫之前,其實共享了同一塊內部儲存區域。在“Mutability”章節我們會深入瞭解陣列的值語義,包括如何為自定義的型別實現“寫時複製”技術

#譯者注

[1]:我覺得嚴格來說是isEmptycout屬性的get方法。

[2]:也就說object.attribute = newValue可以,而object = newObject不可以。

相關文章