集合類型(二)

集合類型模塊分四篇筆記來學習:

  • 第一篇:
  • 數組和可變性
  • 數組的變形
  • 第二篇:
    • 字典和集合
    • 集合協議
  • 第三篇:
    • 集合
  • 第四篇:
    • 索引

本篇開始學習第二篇,Go!

1.字典和集合

Swift 中另一個關鍵的數據結構是字典。字典指的是包含鍵以及它們所對應的值的數據結構。在一個字典中,每個鍵都只能出現一次。
舉個例子,我們現在來為設置界面構建一些基礎代碼。我們先定義了 Settings 協議,遵守這個協議的類型需要提供一個能夠設定它的 UIView 對象。比如對于 String,我們返回 UITextField,而對于布爾值,我們返回 UISwitch:

protocol Setting {
  func settingsView() -> UIView
}

現在我們可以定義一個由鍵值對組成的字典了。這個字典中的鍵是設置項的名字,而值就是這些設定所存儲的值。我們使用 let 來定義字典,也就是說,我們之后不會再去更改它。

let defaultSettings: [String:Setting] = [
  "Airplane Mode": true,
  "Name": "My iPhone",
]

變更

和數組一樣,使用 let 定義的字典是不可變的:你不能向其中添加或者修改條目。如果想要定義一個可變的字典,你需要使用 var 進行聲明。想要將某個值從字典中移除,可以將它設為 nil。

有用的字典擴展

我們可以自己為字典添加一些擴展,比如增加將兩個字典合并的功能。

舉例來說,當我們將設置展示給用戶時,我們希望將它們與用戶存儲的設置進行合并后再一起展示。假設我們已經有一個 storedSettings 方法來讀取用戶所存儲的設置:

現在,我們來為字典增加一個 merge 函數。字典遵守 SequenceType 協議,為了通用性,我們可以讓這個方法接受的參數是任意的遵守 SequenceType 協議、并且能產生 (Key,Value) 鍵值對的類型。這樣一來,我們將不僅能夠合并字典類型,也能對其他類似的類型進行相同的操作。

extension Dictionary {
  mutating func merge<S: SequenceType
  where S.Generator.Element == (Key,Value)>(other: S) {
    for (k, v) in other {
      self[k] = v
    }
  }
}

現在我們可以將存儲的設置與默認設置進行合并了:

var settings = defaultSettings
settings.merge(storedSettings())

另一個有意思的擴展是從一個 (Key, Value) 鍵值對的序列來創建字典。我們可以先創建一個空字典,然后將序列合并到字典中去。這樣一來,我們就可以重用上面的 merge 方法,讓它來做實際的工作:

extension Dictionary {
  init<S: SequenceType where S.Generator.Element == (Key,Value)>(_ sequence: S) {
    self = [:]
    self.merge(sequence)
  }
}

// 所有 alert 默認都是關閉的
let defaultAlarms = (1..<5).map { ("Alarm \($0)", false) }
let alarmsDictionary = Dictionary(defaultAlarms)

在閉包中使用集合

在 Swift 標準庫中,還包含了 Set 類型。Set 是一系列元素的無序集合,每個元素在 Set 中只會出現一次。Set 遵循 ArrayLiteralConvertible 協議,也就是說,我們可以通過使用和數組一樣的字面量語法來初始化一個集合。

let mySet: Set<Int> = [1, 2]

在你的函數中,無論你是否將它們暴露給調用者,字典和集合都會是非常有用的數據結構。舉個例子,如果我們想要為 SequenceType 寫一個擴展,來獲取序列中所有的唯一元素,我們只需要將這些元素放到一個 Set 里,然后返回這個集合的內容就行了。不過,因為 Set 并沒有定義順序,所以這么做是不穩定的,輸入的元素的順序在結果中可能會不一致。為了解決這個問題,我們可以創建一個擴展來解決這個問題,在擴展方法內部我們還是使用 Set 來驗證唯一性:

  extension SequenceType where Generator.Element: Hashable {
    func unique() -> [Generator.Element] {
      var seen: Set<Generator.Element> = []
      return filter {
          if seen.contains($0) {
            return false
          } else {
            seen.insert($0)
            return true
          }
      }
    }
  }

上面這個方法讓我們可以找到序列中的所有不重復的元素,并且維持它們原來的順序。

2.集合協議

留心看看數組、字典以及集合的定義,我們會發現它們都遵守 CollectionType 協議。更進一步,CollectionType 本身是一個遵守 SequenceType 的協議。而序列 (sequence) 使用一個 GeneratorType 來提供它其中的元素。簡單說,生成器 (generator) 知道如何產生新的值,序列知道如何創建一個生成器,而集合 (collection) 為序列添加了有意義的隨機存取的方式。下面開始逆著學習。

生成器

GeneratorType 協議通過兩部分來定義。

  1. 它要求遵守它的類型都需要有一個 Element 關聯類型,這個類型也就是生成器產生的值的類型。(在數組中,生成器的元素類型就是數組的元素類型。在字典中,元素類型是一個同時包含鍵和值的多元組,也就是 (Key, Value))
  2. GeneratorType 定義的第二部分是 next 函數,它返回類型為 Element 的可選值。對于遵守 GeneratorType 的值,你可以一直調用它的 next 函數,直到你得到 nil 值。

結合這兩部分,我們就能得到 GeneratorType 協議的定義了:

  protocol GeneratorType {
      associatedtype Element
      mutating func next() -> Element?
  }

你只能將生成器的值循環一次。也就是說,生成器并沒有值語義。所以我們將會使用 class 來創建生成器,而不使用 struct

我們能寫出的最簡單的生成器是一個在被詢問下一個值時每次都返回常數的生成器:

class ConstantGenerator: GeneratorType {
    typealias Element = Int
    func next() -> Element? {
        return 1
    }
}

或者采用隱式的賦值:

class ConstantGenerator: GeneratorType {
  func next() -> Int? {
    return 1
  }
}

聲明一個斐波那契數的生成者:

class FibsGenerator: GeneratorType {
    var state = (0, 1)
    func next() -> Int? {
      let upcomingNumber = state.0
      state = (state.1, state.0 + state.1)
      return upcomingNumber
    }
}

上面這些生成器都能且只能被迭代一次。如果我們想要再次進行迭代,那么就需要創建一個新的生成器。這也通過它的定義有所暗示:我們將它們聲明成了類而非結構體。在被不同變量共享的時候,只有引用的傳遞,而不發生復制。

Swift 標準庫中的 AnyGenerator 有著曲折的過去。Swift 最開始的時候曾經有個叫做 GeneratorOf 的類型,它是一個結構體,做著與現在 AnyGenerator 類似的事情。在 Swift 2.0 的時候,AnyGenerator 取代了 GeneratorOf,并且它是一個類。不過在 Swift 2.2 里,它又變成了一個結構體。雖然 AnyGenerator 現在是一個結構體,但是它并不具有值語義,因為它其實將生成器的實現封裝到了一個方法的引用類型中去。作為存在狀態的結構,生成器其實在 Swift 標準庫中顯得有那么一點格格不入,因為標準庫里絕大部分類型都是具有值語義的結構體。

避免去復制生成器。當你需要生成器的時候,總是去創建一個新的。我們接下來要說的序列類型 (SequenceType) 就很好地遵守了這一原則。

序列

進行多次迭代是很常見的行為,因此 SequenceType 應運而生。SequenceType 是構建在 GeneratorType 基礎上的一個協議,它需要我們指定一個特定類型的生成器,并且提供一個方法來創建新的生成器:

protocol SequenceType {
  associatedtype Generator: GeneratorType
  func generate() -> Generator
}

舉個例子,想要多次枚舉前面我們提到的前綴的話,我們可以把 PrefixGenerator 放到一個序列中去。我們在這里沒有顯式地指定 GeneratorType 的類型,通過讀取 generate 方法的返回類型,編譯器可以為我們推斷出所需要的 GeneratorType:

struct PrefixSequence: SequenceType {
    let string: String
    func generate() -> PrefixGenerator {
        return PrefixGenerator(string: string)
    }
}

因為我們實現了 SequenceType 協議,我們現在已經可以用 for 循環來迭代給定字符串的所有前綴了:

for prefix in PrefixSequence(string: "Hello") {
  print(prefix)
}

for 循環的工作原理是這樣的:編譯器為序列創建一個新的生成器,然后一直調用這個生成器的 next 方法獲取其中的值,直到它返回 nil。從本質上說,for 就是下面這段代碼的一種簡寫:

var generator = PrefixSequence(string: "Hello").generate()
while let prefix = generator.next() {
  print(prefix)
}

還有一種更簡單的方法來創建生成器和序列。相比于創建一個新的類,我們可以使用 Swift 內建的 AnyGenerator 和 AnySequence 類型。它們接受一個函數作為參數,并根據這個函數為我們構建相應的類型。示例如下:

func fibGenerator() -> AnyGenerator<Int> {
  var state = (0, 1)
  return AnyGenerator {
      let result = state.0
      state = (state.1, state.0 + state.1)
      return result
  }
}

通過生成器創建序列的更加容易:

let fibSequence = AnySequence(fibGenerator)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,933評論 18 139
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 3,885評論 1 10
  • 包含內容 maven屬性 構建環境的差異 資源過濾 Maven Profile Web資源過濾 在profile中...
    zlcook閱讀 1,109評論 0 7
  • 如果沒有孩子,我無法想象今天自己的模樣。因為在有孩子之前,我從來不知道怕,也不太會忍耐,我的人生就是快意恩...
    悠然_3c09閱讀 199評論 0 2
  • 第一眼的你 美OR丑 善OR惡 第一眼的你 真真假假 假假真真 第一眼你看到的是什么 第一眼在向你訴說什么 第一眼...
    劉赟閱讀 224評論 0 0