我們在swift中最常用的集合型別就是陣列了。陣列,簡單來說,就是一列東西。比如我們可以這樣建立一個數字的陣列:
let fibs = [0, 1, 1, 2, 3, 5]
複製程式碼
陣列有一些常用操作,比如isEmpty
和cout
方法1,分別獲取陣列的第一個和最後一個元素(除非陣列為空)。陣列還允許使用下標指令碼(subscript),通過指定一個下標直接訪問元素。但使用下標指令碼是不安全的操作,因為在獲取元素之前,你需要確保下標沒有越界,否則程式就會崩潰。
如果我們想要修改剛剛定義的陣列(比如使用apppend
方法),我們會得到一個編譯錯誤。這是因為通過let
關鍵字,陣列被定義為了常量(constant)。在很多情況下這麼做毫無問題,因為它可以防止你偶然間修改到這個陣列。我們需要使用var
關鍵字來讓這個陣列成為一個變數(variable)。
var mutableFibs = [0, 1, 1, 2, 3, 5]
複製程式碼
這樣就容易在陣列後追加一個或一組元素了:
mutableFibs.append(8)
mutableFibs.appendContentsOf([13,21])
複製程式碼
把var
和let
區別開來有很多好處。被定義為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中,我們不需要NSArray
和NSMutableArray
這兩種陣列了,只需要一個Swift自己的陣列便足矣。陣列的可變性由var
和let
關鍵字來決定。與OC不同的是,對某一個陣列的引用是不會共享的,也就是當你用let
來定義第二個陣列的時候,你可以確保這第二個陣列是不會改變的。
確實,經常複製陣列會帶來效能上的問題,但實際上,Swift標註庫裡所有的集合型別在實現時都採用了“寫時複製(copy-on-write)”技術以確保資料只有在真正需要的時候才會被複制。所以在之前的例子中,x
和y
在y.append(4)
呼叫之前,其實共享了同一塊內部儲存區域。在“Mutability”章節我們會深入瞭解陣列的值語義,包括如何為自定義的型別實現“寫時複製”技術
#譯者注
[1]:我覺得嚴格來說是isEmpty
和cout
屬性的get方法。
[2]:也就說object.attribute = newValue
可以,而object = newObject
不可以。