翻譯:zqp
校對:shinyzhu, stanzhai, feiin

集合類型 (Collection Types)


本頁包含內容:

Swift 語言提供經典的數組和字典兩種集合類型來存儲集合數據。數組用來按順序存儲相同類型的數據。字典雖然無序存儲相同類型數據值但是需要由獨有的標識符引用和尋址(就是鍵值對)。

Swift 語言裡的數組和字典中存儲的數據值類型必須明確。 這意味著我們不能把不正確的數據類型插入其中。 同時這也說明我們完全可以對獲取出的值類型非常自信。 Swift 對顯式類型集合的使用確保了我們的代碼對工作所需要的類型非常清楚,也讓我們在開發中可以早早地找到任何的類型不匹配錯誤。

注意:
Swift 的數組結構在被聲明成常量和變量或者被傳入函數與方法中時會相對於其他類型展現出不同的特性。 獲取更多信息請參見集合的可變性集合在賦值和複製中的行為章節。

數組

數組使用有序列表存儲同一類型的多個值。相同的值可以多次出現在一個數組的不同位置中。

Swift 數組特定於它所存儲元素的類型。這與 Objective-C 的 NSArray 和 NSMutableArray 不同,這兩個類可以存儲任意類型的對象,並且不提供所返回對象的任何特別信息。在 Swift 中,數據值在被存儲進入某個數組之前類型必須明確,方法是通過顯式的類型標注或類型推斷,而且不是必須是class類型。例如: 如果我們創建了一個Int值類型的數組,我們不能往其中插入任何不是Int類型的數據。 Swift 中的數組是類型安全的,並且它們中包含的類型必須明確。

數組的簡單語法

寫 Swift 數組應該遵循像Array<SomeType>這樣的形式,其中SomeType是這個數組中唯一允許存在的數據類型。 我們也可以使用像[SomeType]這樣的簡單語法。 儘管兩種形式在功能上是一樣的,但是推薦較短的那種,而且在本文中都會使用這種形式來使用數組。

數組構造語句

我們可以使用字面量來進行數組構造,這是一種用一個或者多個數值構造數組的簡單方法。字面量是一系列由逗號分割並由方括號包含的數值。 [value 1, value 2, value 3]

下面這個例子創建了一個叫做shoppingList並且存儲字符串的數組:

  1. var shoppingList: [String] = ["Eggs", "Milk"]
  2. // shoppingList 已經被構造並且擁有兩個初始項。

shoppingList變量被聲明為「字符串值類型的數組「,記作[String]。 因為這個數組被規定只有String一種數據結構,所以只有String類型可以在其中被存取。 在這裡,shoppinglist數組由兩個String值("Eggs""Milk")構造,並且由字面量定義。

注意:
Shoppinglist數組被聲明為變量(var關鍵字創建)而不是常量(let創建)是因為以後可能會有更多的數據項被插入其中。

在這個例子中,字面量僅僅包含兩個String值。匹配了該數組的變量聲明(只能包含String的數組),所以這個字面量的分配過程就是允許用兩個初始項來構造shoppinglist

由於 Swift 的類型推斷機制,當我們用字面量構造只擁有相同類型值數組的時候,我們不必把數組的類型定義清楚。 shoppinglist的構造也可以這樣寫:

  1. var shoppingList = ["Eggs", "Milk"]

因為所有字面量中的值都是相同的類型,Swift 可以推斷出[String]shoppinglist中變量的正確類型。

訪問和修改數組

我們可以通過數組的方法和屬性來訪問和修改數組,或者下標語法。 還可以使用數組的只讀屬性count來獲取數組中的數據項數量。

  1. println("The shopping list contains \(shoppingList.count) items.")
  2. // 輸出"The shopping list contains 2 items."(這個數組有2個項)

使用布爾項isEmpty來作為檢查count屬性的值是否為 0 的捷徑。

  1. if shoppingList.isEmpty {
  2. println("The shopping list is empty.")
  3. } else {
  4. println("The shopping list is not empty.")
  5. }
  6. // 打印 "The shopping list is not empty."(shoppinglist不是空的)

也可以使用append方法在數組後面添加新的數據項:

  1. shoppingList.append("Flour")
  2. // shoppingList 現在有3個數據項,有人在攤煎餅

除此之外,使用加法賦值運算符(+=)也可以直接在數組後面添加數據項:

  1. shoppingList += "Baking Powder"
  2. // shoppingList 現在有四項了

我們也可以使用加法賦值運算符(+=)直接添加擁有相同類型數據的數組。

  1. shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
  2. // shoppingList 現在有7項了

可以直接使用下標語法來獲取數組中的數據項,把我們需要的數據項的索引值放在直接放在數組名稱的方括號中:

  1. var firstItem = shoppingList[0]
  2. // 第一項是 "Eggs"

注意第一項在數組中的索引值是0而不是1。 Swift 中的數組索引總是從零開始。

我們也可以用下標來改變某個已有索引值對應的數據值:

  1. shoppingList[0] = "Six eggs"
  2. // 其中的第一項現在是 "Six eggs" 而不是 "Eggs"

還可以利用下標來一次改變一系列數據值,即使新數據和原有數據的數量是不一樣的。下面的例子把"Chocolate Spread""Cheese",和"Butter"替換為"Bananas""Apples"

  1. shoppingList[4...6] = ["Bananas", "Apples"]
  2. // shoppingList 現在有六項

注意:
我們不能使用下標語法在數組尾部添加新項。如果我們試著用這種方法對索引越界的數據進行檢索或者設置新值的操作,我們會引發一個運行期錯誤。我們可以使用索引值和數組的count屬性進行比較來在使用某個索引之前先檢驗是否有效。除了當count等於 0 時(說明這是個空數組),最大索引值一直是count - 1,因為數組都是零起索引。

調用數組的insert(atIndex:)方法來在某個具體索引值之前添加數據項:

  1. shoppingList.insert("Maple Syrup", atIndex: 0)
  2. // shoppingList 現在有7項
  3. // "Maple Syrup" 現在是這個列表中的第一項

這次insert函數調用把值為"Maple Syrup"的新數據項插入列表的最開始位置,並且使用0作為索引值。

類似的我們可以使用removeAtIndex方法來移除數組中的某一項。這個方法把數組在特定索引值中存儲的數據項移除並且返回這個被移除的數據項(我們不需要的時候就可以無視它):

  1. let mapleSyrup = shoppingList.removeAtIndex(0)
  2. // 索引值為0的數據項被移除
  3. // shoppingList 現在只有6項,而且不包括Maple Syrup
  4. // mapleSyrup常量的值等於被移除數據項的值 "Maple Syrup"

數據項被移除後數組中的空出項會被自動填補,所以現在索引值為0的數據項的值再次等於"Six eggs":

  1. firstItem = shoppingList[0]
  2. // firstItem 現在等於 "Six eggs"

如果我們只想把數組中的最後一項移除,可以使用removeLast方法而不是removeAtIndex方法來避免我們需要獲取數組的count屬性。就像後者一樣,前者也會返回被移除的數據項:

  1. let apples = shoppingList.removeLast()
  2. // 數組的最後一項被移除了
  3. // shoppingList現在只有5項,不包括cheese
  4. // apples 常量的值現在等於"Apples" 字符串

數組的遍歷

我們可以使用for-in循環來遍歷所有數組中的數據項:

  1. for item in shoppingList {
  2. println(item)
  3. }
  4. // Six eggs
  5. // Milk
  6. // Flour
  7. // Baking Powder
  8. // Bananas

如果我們同時需要每個數據項的值和索引值,可以使用全局enumerate函數來進行數組遍歷。enumerate返回一個由每一個數據項索引值和數據值組成的元組。我們可以把這個元組分解成臨時常量或者變量來進行遍歷:

  1. for (index, value) in enumerate(shoppingList) {
  2. println("Item \(index + 1): \(value)")
  3. }
  4. // Item 1: Six eggs
  5. // Item 2: Milk
  6. // Item 3: Flour
  7. // Item 4: Baking Powder
  8. // Item 5: Bananas

更多關於for-in循環的介紹請參見for 循環

創建並且構造一個數組

我們可以使用構造語法來創建一個由特定數據類型構成的空數組:

  1. var someInts = [Int]()
  2. println("someInts is of type [Int] with \(someInts.count) items。")
  3. // 打印 "someInts is of type [Int] with 0 items。"(someInts是0數據項的Int[]數組)

注意someInts被設置為一個[Int]構造函數的輸出所以它的變量類型被定義為[Int]

除此之外,如果代碼上下文中提供了類型信息, 例如一個函數參數或者一個已經定義好類型的常量或者變量,我們可以使用空數組語句創建一個空數組,它的寫法很簡單:[](一對空方括號):

  1. someInts.append(3)
  2. // someInts 現在包含一個INT值
  3. someInts = []
  4. // someInts 現在是空數組,但是仍然是[Int]類型的。

Swift 中的Array類型還提供一個可以創建特定大小並且所有數據都被默認的構造方法。我們可以把準備加入新數組的數據項數量(count)和適當類型的初始值(repeatedValue)傳入數組構造函數:

  1. var threeDoubles = [Double](count: 3, repeatedValue:0.0)
  2. // threeDoubles 是一種 [Double]數組, 等於 [0.0, 0.0, 0.0]

因為類型推斷的存在,我們使用這種構造方法的時候不需要特別指定數組中存儲的數據類型,因為類型可以從默認值推斷出來:

  1. var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
  2. // anotherThreeDoubles is inferred as [Double], and equals [2.5, 2.5, 2.5]

最後,我們可以使用加法操作符(+)來組合兩種已存在的相同類型數組。新數組的數據類型會被從兩個數組的數據類型中推斷出來:

  1. var sixDoubles = threeDoubles + anotherThreeDoubles
  2. // sixDoubles 被推斷為 [Double], 等於 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

字典

字典是一種存儲多個相同類型的值的容器。每個值(value)都關聯唯一的鍵(key),鍵作為字典中的這個值數據的標識符。和數組中的數據項不同,字典中的數據項並沒有具體順序。我們在需要通過標識符(鍵)訪問數據的時候使用字典,這種方法很大程度上和我們在現實世界中使用字典查字義的方法一樣。

Swift 的字典使用時需要具體規定可以存儲鍵和值類型。不同於 Objective-C 的NSDictionaryNSMutableDictionary 類可以使用任何類型的對象來作鍵和值並且不提供任何關於這些對象的本質信息。在 Swift 中,在某個特定字典中可以存儲的鍵和值必須提前定義清楚,方法是通過顯性類型標注或者類型推斷。

Swift 的字典使用Dictionary<KeyType, ValueType>定義,其中KeyType是字典中鍵的數據類型,ValueType是字典中對應於這些鍵所存儲值的數據類型。

KeyType的唯一限制就是可哈希的,這樣可以保證它是獨一無二的,所有的 Swift 基本類型(例如StringIntDoubleBool)都是默認可哈希的,並且所有這些類型都可以在字典中當做鍵使用。未關聯值的枚舉成員(參見枚舉)也是默認可哈希的。

字典字面量

我們可以使用字典字面量來構造字典,它們和我們剛才介紹過的數組字面量擁有相似語法。一個字典字面量是一個定義擁有一個或者多個鍵值對的字典集合的簡單語句。

一個鍵值對是一個key和一個value的結合體。在字典字面量中,每一個鍵值對的鍵和值都由冒號分割。這些鍵值對構成一個列表,其中這些鍵值對由方括號包含並且由逗號分割:

  1. [key 1: value 1, key 2: value 2, key 3: value 3]

下面的例子創建了一個存儲國際機場名稱的字典。在這個字典中鍵是三個字母的國際航空運輸相關代碼,值是機場名稱:

  1. var airports: Dictionary<String, String> = ["TYO": "Tokyo", "DUB": "Dublin"]

airports字典被定義為一種Dictionary<String, String>,它意味著這個字典的鍵和值都是String類型。

注意:
airports字典被聲明為變量(用var關鍵字)而不是常量(let關鍵字)因為後來更多的機場信息會被添加到這個示例字典中。

airports字典使用字典字面量初始化,包含兩個鍵值對。第一對的鍵是TYO,值是Tokyo。第二對的鍵是DUB,值是Dublin

這個字典語句包含了兩個String: String類型的鍵值對。它們對應airports變量聲明的類型(一個只有String鍵和String值的字典)所以這個字典字面量是構造兩個初始數據項的airport字典。

和數組一樣,如果我們使用字面量構造字典就不用把類型定義清楚。airports的也可以用這種方法簡短定義:

  1. var airports = ["TYO": "Tokyo", "DUB": "Dublin"]

因為這個語句中所有的鍵和值都分別是相同的數據類型,Swift 可以推斷出Dictionary<String, String>airports字典的正確類型。

讀取和修改字典

我們可以通過字典的方法和屬性來讀取和修改字典,或者使用下標語法。和數組一樣,我們可以通過字典的只讀屬性count來獲取某個字典的數據項數量:

  1. println("The dictionary of airports contains \(airports.count) items.")
  2. // 打印 "The dictionary of airports contains 2 items."(這個字典有兩個數據項)

我們也可以在字典中使用下標語法來添加新的數據項。可以使用一個合適類型的 key 作為下標索引,並且分配新的合適類型的值:

  1. airports["LHR"] = "London"
  2. // airports 字典現在有三個數據項

我們也可以使用下標語法來改變特定鍵對應的值:

  1. airports["LHR"] = "London Heathrow"
  2. // "LHR"對應的值 被改為 "London Heathrow

作為另一種下標方法,字典的updateValue(forKey:)方法可以設置或者更新特定鍵對應的值。就像上面所示的示例,updateValue(forKey:)方法在這個鍵不存在對應值的時候設置值或者在存在時更新已存在的值。和上面的下標方法不一樣,這個方法返回更新值之前的原值。這樣方便我們檢查更新是否成功。

updateValue(forKey:)函數會返回包含一個字典值類型的可選值。舉例來說:對於存儲String值的字典,這個函數會返回一個String?或者「可選 String」類型的值。如果值存在,則這個可選值值等於被替換的值,否則將會是nil

  1. if let oldValue = airports.updateValue("Dublin Internation", forKey: "DUB") {
  2. println("The old value for DUB was \(oldValue).")
  3. }
  4. // 輸出 "The old value for DUB was Dublin."(DUB原值是dublin)

我們也可以使用下標語法來在字典中檢索特定鍵對應的值。由於使用一個沒有值的鍵這種情況是有可能發生的,可選類型返回這個鍵存在的相關值,否則就返回nil

  1. if let airportName = airports["DUB"] {
  2. println("The name of the airport is \(airportName).")
  3. } else {
  4. println("That airport is not in the airports dictionary.")
  5. }
  6. // 打印 "The name of the airport is Dublin Internation."(機場的名字是都柏林國際)

我們還可以使用下標語法來通過給某個鍵的對應值賦值為nil來從字典裡移除一個鍵值對:

  1. airports["APL"] = "Apple Internation"
  2. // "Apple Internation"不是真的 APL機場, 刪除它
  3. airports["APL"] = nil
  4. // APL現在被移除了

另外,removeValueForKey方法也可以用來在字典中移除鍵值對。這個方法在鍵值對存在的情況下會移除該鍵值對並且返回被移除的value或者在沒有值的情況下返回nil

  1. if let removedValue = airports.removeValueForKey("DUB") {
  2. println("The removed airport's name is \(removedValue).")
  3. } else {
  4. println("The airports dictionary does not contain a value for DUB.")
  5. }
  6. // prints "The removed airport's name is Dublin International."

字典遍歷

我們可以使用for-in循環來遍歷某個字典中的鍵值對。每一個字典中的數據項都由(key, value)元組形式返回,並且我們可以使用臨時常量或者變量來分解這些元組:

  1. for (airportCode, airportName) in airports {
  2. println("\(airportCode): \(airportName)")
  3. }
  4. // TYO: Tokyo
  5. // LHR: London Heathrow

for-in循環請參見For 循環

我們也可以通過訪問它的keys或者values屬性(都是可遍歷集合)檢索一個字典的鍵或者值:

  1. for airportCode in airports.keys {
  2. println("Airport code: \(airportCode)")
  3. }
  4. // Airport code: TYO
  5. // Airport code: LHR
  6. for airportName in airports.values {
  7. println("Airport name: \(airportName)")
  8. }
  9. // Airport name: Tokyo
  10. // Airport name: London Heathrow

如果我們只是需要使用某個字典的鍵集合或者值集合來作為某個接受Array實例 API 的參數,可以直接使用keys或者values屬性直接構造一個新數組:

  1. let airportCodes = Array(airports.keys)
  2. // airportCodes is ["TYO", "LHR"]
  3. let airportNames = Array(airports.values)
  4. // airportNames is ["Tokyo", "London Heathrow"]

注意:
Swift 的字典類型是無序集合類型。其中字典鍵,值,鍵值對在遍歷的時候會重新排列,而且其中順序是不固定的。

創建一個空字典

我們可以像數組一樣使用構造語法創建一個空字典:

  1. var namesOfIntegers = Dictionary<Int, String>()
  2. // namesOfIntegers 是一個空的 Dictionary<Int, String>

這個例子創建了一個Int, String類型的空字典來儲存英語對整數的命名。它的鍵是Int型,值是String型。

如果上下文已經提供了信息類型,我們可以使用空字典字面量來創建一個空字典,記作[:](中括號中放一個冒號):

  1. namesOfIntegers[16] = "sixteen"
  2. // namesOfIntegers 現在包含一個鍵值對
  3. namesOfIntegers = [:]
  4. // namesOfIntegers 又成為了一個 Int, String類型的空字典

注意:
在後台,Swift 的數組和字典都是由泛型集合來實現的,想瞭解更多泛型和集合信息請參見泛型

集合的可變性

數組和字典都是在單個集合中存儲可變值。如果我們創建一個數組或者字典並且把它分配成一個變量,這個集合將會是可變的。這意味著我們可以在創建之後添加更多或移除已存在的數據項來改變這個集合的大小。與此相反,如果我們把數組或字典分配成常量,那麼它就是不可變的,它的大小不能被改變。

對字典來說,不可變性也意味著我們不能替換其中任何現有鍵所對應的值。不可變字典的內容在被首次設定之後不能更改。 不可變性對數組來說有一點不同,當然我們不能試著改變任何不可變數組的大小,但是我們可以重新設定相對現存索引所對應的值。這使得 Swift 數組在大小被固定的時候依然可以做的很棒。

Swift 數組的可變性行為同時影響了數組實例如何被分配和修改,想獲取更多信息,請參見集合在賦值和複製中的行為

注意:
在我們不需要改變數組大小的時候創建不可變數組是很好的習慣。如此 Swift 編譯器可以優化我們創建的集合。