Swift 中的 Collections (一):Array

這篇文章重點不是介紹Array Dictonary Set Range的簡單使用的,文章重點是 Swift Collection中你可能不知道的東西,文章既有如何創建一個數組,也有ArraySlice(數組切片)的說明。之所以有一些很基礎的東西,是看到很多Swift使用者在一知半解,在項目里寫了很多怪異的寫法,就算掌握了,跟著過一遍,也花不了幾分鐘。

Array : 數組

創建一個數組

  • 創建一個空數組
let array1: Array<Int> = Array<Int>()
let array2: [Int] = []
  • 創建帶有初始值的數組
let array1 = [1, 2, 3] // [1, 2, 3, 4, 5]
let array2 = [Int](repeating: 0, count: 3) // [0, 0, 0]

可變性

在上邊,我們聲明數組是用let關鍵字,也就是我們把數組聲明成常量了,如果像下邊這么寫,會得到一個編譯錯誤:

let array = [1, 2]
array.append(3)
error: cannot use mutating member on immutable value: 'array' is a 'let' constant
array.append(3)

如果需要改變數組中的元素,需要使用var關鍵詞定義:

var array = [1, 2]
array.append(3)    //[1, 2, 3]
array.append(contentsOf: [4, 5])    //[1, 2, 3, 4, 5]

數組和標準庫中的所有集合類型一樣,是具有值語義的。當你創建一個新的數組變量并且把一個已經存在的數組賦值給它的時候,這個數組的內容會被復制。舉個例子,在下面的代碼中,x 將不會被更改:

var x = [1,2,3]
var y = x
y.append(4)
y // [1, 2, 3, 4]
x // [1, 2, 3]

var y = x 語句復制了 x,所以在將 4 添加到 y 末尾的時候,x 并不會發生改變,它的值依然是 [1,2,3]。當你把一個數組傳遞給一個函數時,會發生同樣的事情;方法將得到這個數組的一份本地復制,所有對它的改變都不會影響調用者所持有的數組。

Array常用操作

添加刪除元素

var array = [1, 2]

// 向數組添加一個元素
array.append(3)    //[1, 2, 3]

// 拼接數組
array.append(contentsOf: [4, 5])    //[1, 2, 3, 4, 5]

// 插入元素
array.insert(6, at: array.endIndex)   // [1, 2, 3, 4, 5, 6]

// 刪除對應index的元素
array.remove(at: 5) // [1, 2, 3, 4, 5]

// 刪除最后一個元素
array.removeLast() // [1, 2, 3, 4]

count & isEmtpy & forEach

let array = [1, 2]

// 數組元素個數
array.count    // 2
// 數組是否為空
if array.isEmpty {
    print("array is empty")
}
// 遍歷數組
array.forEach {element in
    print(element)
}

map & flatmap & filter & reduce

map & flatmap & filter & reduce 的用法,我之前寫過,
點擊這里

訪問數組中元素

定義一個數組

var array = [1, 2, 3, 4, 5]

使用下標訪問元素:

array[0]    // 1
array[1]    // 2

array[10]    //超出長度,carsh

使用range operator訪問范圍內元素:

array[0...2] // [1, 2, 3]
array[0..<2] // [1, 2]

使用下標訪問數組元素會有一個嚴重的問題,就是下標超出數組范圍會carsh,說好的Swift更安全呢? 其實,使用下邊訪問數組元素是Swift不推薦的方法Swift 3 中傳統的 C 風格的 for 循環被移除了,這是 Swift 不鼓勵你去做索引計算的一個標志。手動計算和使用索引值往往可能帶來很多潛在的 bug,所以最好避免這么做。
但是有些時候我們又不得不使用索引,為什么不在使用索引時提供可選值呢,比如:

let i = array[10] 
i: Optional<Int> // 讓取到的值是一個optional,這樣可以避免崩潰

因為當你使用數組索引的時候,Swift默認你應該已經深思熟慮,對背后的索引計算邏輯進行過認真思考。不提供可選值。Swift默認你信任你的代碼,在這個前提下,如果每次都要對獲取的結果進行解包的話就顯得多余了。而且如果提供可選值,一方面十分麻煩,每次都要解包,另一方面也是一個壞習慣。當強制解包變成一種習慣后,很可能你會不小心強制解包了本來不應該解包的東西。所以,為了避免這個行為變成習慣,數組根本沒有給你可選值的選項。
如果你真的想要通過下標訪問元素,同時還想防止崩潰的產生,可以通過給Array 進行拓展來實現這個功能:

extension Array {
    subscript(safe idx: Int) -> Element? {
        return idx < endIndex ? self[idx] : nil
    }
}

取出數組元素的時候像這樣:

array[safe: 100]  // nil ,不會崩潰
array[safe: 1]  //Optional<Int> ,需要解包

Swift 不鼓勵你去做索引計算主要原因是我們可以使用數組切片。什么叫數組切片,下邊再解釋。先看在數組中,不使用下標直接訪問元素,如何取到數組中的元素:

// 迭代數組
for x in array {
    ...
}
// 迭代除了第一個元素以外的數組其余部分
for x in array.dropFirst() {
   ...
}
// 迭代除了最后 5 個元素以外的數組
for x in array.dropLast(5) {
   ...
}
// 列舉數組中的元素和對應的下標
for (num, element) in collection.enumerated() {
    ...
}
// 尋找一個指定元素的位置
if let idx = array.index { someMatchingLogic($0) } {
    ...
}
// 對數組中的所有元素進行變形
array.map { someTransformation($0) } 
// 篩選出符合某個標準的元素
array.filter { someCriteria($0) }

ArraySlice : 數組切片

在上邊的例子中,我們發現array.dropLast(5) 或者 array[0...2] 得到的并不是一個 Array<Int> 類型的數組,而是一個 ArraySliceArraySlice 也就是數組切片,切片類型只是數組的一種表示方式,它背后的數據仍然是原來的數組,只不過是用切片的方式來進行表示。這意味著原來的數組并不需要被復制。ArraySlice 具有的方法和 Array 上定義的方法是一致的,因此你可以把它們當做數組來進行處理。簡單來說,就是Array某一段內容的view,它不真正保存數組的內容,只保存這個view引用的數組的范圍:

ArraySlice

(圖自:《Swift 進階》)
圖中,fibs 表示一個斐波那契數列數組,即 [0, 1, 1, 2, 3, 5 ,...]
slice表示fibsArraySlice
fibs中包含了數組的長度 6 和一個指向 數組內存地址的指針prt
slice包含了一個同樣指向 數組內存地址的指針prt,同時還有view起始Index 1,和結束Index6,我們可以通過改變這個位置的起止區間,來表示fibs這個數組的不同view

如果你需要將切片轉換為數組的話,你可以通過將切片傳遞給 Array 的構建方法來完成:

Array(slice)

Array 與 NSArray

我們知道在Swift中,Array是按照值語義實現的,當我們這么寫的時候,其實是拷貝了一份新的Array

var array1 = [0, 1, 2] // [0, 1, 2]
let array2 = array1 // [0, 1, 2]

// 對array1進行操作不會影響array2
array1.append(3) // array1: [0, 1, 2, 3] ; array2: [0, 1, 2]

// 因為array2 被用let 聲明為常量,所以是不可變的
array2.append(3)  // Compile error 

Swift對值類型的拷貝處理其實是使用copy on write的方式,當你復制Array時,真正的復制是不會發生的,兩個數組同樣引用同一個內存地址。只有當你修改了其中一個Array的內容時,才會復制一份Array并更改指針指向位置。

Foundatioin中,數組包括 NSArrayNSMutableArray
NSArrayNSMutableArray都是類對象,數組是否可以被修改是通過NSArrayNSMutableArray這兩個類型來決定的,我們知道,在Objective-C中,復制它們執行的是引用語義,如果在Swift中使用 NSArrayNSMutableArray :會產生讓人很奇怪的感覺:

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

在上邊的一句中:我們明明使用了 letarray 聲明成一個常量,但是其實是可以通過:array.add(3) 來向 array 添加一個元素的,編譯器并不會報錯。

當我們用var聲明一個NSArray時,我們希望他是可變數組,其實情況呢:

var mutableArray = NSArray(array: [1, 2, 3])

因為mutableArrayNSArray 類型的,你根本無法調用改變 mutableArray 元素的方法。

結論: 當我們使用 NSArrayNSMutableArray 時,Swift中的 varlet 只控制對應的變量是否可以被賦值成新的對象,并不能控制數組元素是否可以修改。

因為 NSArrayNSMutableArray 復制時執行的是引用語義,他們指向同一個內存地址,改變其中一個的元素時,另一個的也會改變:

let array1 = NSMutableArray(array: [1, 2, 3])
let array2: NSArray = array1

array1.insert(0, at: 0) // array1:[0, 1, 2, 3] ;array2:[0, 1, 2, 3]

為了在使用 NSArray 對象時,執行值語義,我們必須使用它的copy方法復制所有的元素:

let array1 = NSMutableArray(array: [1, 2, 3])
let array2: NSArray = array1
let deepCopyArray = array1.copy() as! NSArray

array1.insert(0, at: 0) // array1:[0, 1, 2, 3] ;array2:[0, 1, 2, 3] ; deepCopyArray:[1, 2, 3]
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容