若是有這樣的一個需求,我但願能像數組同樣,用 for 循環遍歷一個類或結構體中的全部屬性。就像下面這樣:git
let persion = Persion()
for i in persion {
print(i)
}
複製代碼
要實現這樣的需求,咱們須要讓自定義的類型遵照 Sequence 協議。github
Sequence 協議是集合類型結構中的基礎。一個序列 (sequence) 表明的是一系列具備相同類型的值,你能夠對這些值進行迭代。Sequence 協議提供了許多強大的功能,知足該協議的類型均可以直接使用這些功能。上面這樣步進式的迭代元素的能力看起來十分簡單,但它倒是 Sequence 能夠提供這些強大功能的基礎。swift
知足 Sequence 協議的要求十分簡單,你須要作的全部事情就是提供一個返回迭代器 (iterator) 的 makeIterator()
方法:api
public protocol Sequence {
associatedtype Iterator : IteratorProtocol
public func makeIterator() -> Self.Iterator
// ...
}
複製代碼
在 Sequence 協議有個關聯類型 Iterator,並且它必須遵照 IteratorProtocol 協議。從這裏咱們能夠看出 Sequence 是一個能夠建立迭代器協議的類型。因此在搞清楚它的步進式的迭代元素能力以前,有必要了解一下迭代器是什麼。數組
序列經過建立一個迭代器來提供對元素的訪問。迭代器每次產生一個序列的值,而且當遍歷序列時對遍歷狀態進行管理。在 IteratorProtocol 協議中惟一的一個方法是 next(),這個方法須要在每次被調用時返回序列中的下一個值。當序列被耗盡時,next() 應該返回 nil,否則迭代器就會一直工做下去,直到資源被耗盡爲止。bash
IteratorProtocol 的定義很是簡單:閉包
public protocol IteratorProtocol {
associatedtype Element
public mutating func next() -> Self.Element?
}
複製代碼
關聯類型 Element 指定了迭代器產生的值的類型。這裏next()
被標記了 mutating,代表了迭代器是能夠存在可變的狀態的。這裏的 mutating 也不是必須的,若是你的迭代器返回的值並無改變迭代器自己,那麼沒有 mutating 也是沒有任何問題的。 不過幾乎全部有意義的迭代器都會要求可變狀態,這樣它們纔可以管理在序列中的當前位置。app
對 Sequence 和 IteratorProtocol 有了基礎瞭解後,要實現開頭提到的需求就很簡單了。好比我想迭代輸出一個 Person 實例的全部屬性,咱們能夠這樣作:dom
struct Persion: Sequence {
var name: String
var age: Int
var email: String
func makeIterator() -> MyIterator {
return MyIterator(obj: self)
}
}
複製代碼
Persion 遵照了 Sequence 協議,並返回了一個自定義的迭代器。迭代器的實現也很簡單:編輯器
struct MyIterator: IteratorProtocol {
var children: Mirror.Children
init(obj: Persion) {
children = Mirror(reflecting: obj).children
}
mutating func next() -> String? {
guard let child = children.popFirst() else { return nil }
return "\(child.label.wrapped) is \(child.value)"
}
}
複製代碼
迭代器中的 children
是 AnyCollection<Mirror.Child>
的集合類型,每次迭代返回一個值後,更新 children
這個狀態,這樣咱們的迭代器就能夠持續的輸出正確的值了,直到輸出完 children
中的全部值。
如今可使用 for 循環輸出 Persion 中全部的屬性值了:
for item in Persion.author {
print(item)
}
// out put:
// name is jewelz
// age is 23
// email is hujewelz@gmail.com
複製代碼
若是如今有另一個結構體或類也須要迭代輸出因此屬性呢?,這很好辦,讓咱們的結構體遵照 Sequence 協議,並返回一個咱們自定義的迭代器就能夠了。這種拷貝代碼的方式確實能知足需求,可是若是咱們利用協議拓展就能寫出更易於維護的代碼,相似下面這樣:
struct _Iterator: IteratorProtocol {
var children: Mirror.Children
init(obj: Any) {
children = Mirror(reflecting: obj).children
}
mutating func next() -> String? {
guard let child = children.popFirst() else { return nil }
return "\(child.label.wrapped) is \(child.value)"
}
}
protocol Sequencible: Sequence { }
extension Sequencible {
func makeIterator() -> _Iterator {
return _Iterator(obj: self)
}
}
複製代碼
這裏我定義了一個繼承 Sequence 的空協議,是爲了避免影響 Sequence 的默認行爲。如今只要咱們自定義的類或結構體遵照 Sequencible 就能使用 for 循環輸出其全部屬性值了。就像下面這樣:
struct Demo: Sequencible {
var name = "Sequence"
var author = Persion.author
}
複製代碼
如今需求又變了,我想將全部遵照了 Sequencible 協議的任何序列存到一個數組中,而後 for 循環遍歷數組中的元素,由於數組中的元素都遵照了 Sequencible 協議,因此又可使用 for 循環輸出其全部屬性,就像下面這樣:
for obj in array {
for item in obj {
print(item)
}
}
複製代碼
那麼這裏的 array 應該定義成什麼類型呢?定義成 [Any] 類型確定是不行的,這樣的話在循環中得將 item 強轉爲 Sequencible,那麼是否能夠定義成 [Sequencible] 類型呢?答案是否認的。當這樣定義時編輯器會報出這樣的錯誤:
Protocol 'Sequencible' can only be used as a generic constraint because it has Self or associated type requirements
複製代碼
熟悉 Swift 協議的同窗應該對這個報錯比較熟了。就是說含有 Self 或者關聯類型的協議,只能被看成泛型約束使用。因此像下面這樣定義咱們的 array 是行不通的。
let sequencibleStore: [Sequencible] = [Persion.author, Demo()]
複製代碼
若是有這樣一個類型,能夠隱藏 Sequencible 這個具體的類型不就解決這個問題了嗎?這種將指定類型移除的過程,就被稱爲類型擦除。
回想一下 Sequence 協議的內容,咱們只要經過 makeIterator()
返回一個迭代器就能夠了。那麼咱們能夠實現一個封裝類(結構體也是同樣的),裏面用一個屬性存儲了迭代器的實現,而後在 makeIterator()
方法中經過存儲的這個屬性構造一個迭代器。相似這樣:
func makeIterator() -> _AnyIterator<Element> {
return _AnyIterator(iteratorImpl)
}
複製代碼
咱們的這個封裝能夠這樣定義:
struct _AnySequence<Element>: Sequence {
private var iteratorImpl: () -> Element?
}
複製代碼
對於剛剛上面的那個數組就能夠這樣初始化了:
let sequencibleStore: [_AnySequence<String>] = [_AnySequence(Persion.author), _AnySequence(Demo())]
複製代碼
這裏的 _AnySequence 就將具體的 Sequence 類型隱藏了,調用者只知道數組中的元素是一個能夠迭代輸出字符串類型的序列。
如今咱們能夠一步步來實現上面的 _AnyIterator 和 _AnySequence。_AnyIterator 的實現跟上面提到的 _AnySequence 的思路一致。咱們不直接存儲迭代器,而是讓封裝類存儲迭代器的 next 函數。要作到這一點,咱們必須首先將 iterator 參數複製到一個變量中,這樣咱們就能夠調用它的 next 方法了。下面是具體實現:
struct _AnyIterator<Element> {
var nextImpl: () -> Element?
}
extension _AnyIterator: IteratorProtocol {
init<I>(_ iterator: I) where Element == I.Element, I: IteratorProtocol {
var mutatedIterator = iterator
nextImpl = { mutatedIterator.next() }
}
mutating func next() -> Element? {
return nextImpl()
}
}
複製代碼
如今,在 _AnyIterator 中,迭代器的具體類型(好比上面用到的_Iterator)只有在建立實例的時候被指定。在那以後具體的類型就被隱藏了起來。咱們可使用任意類型的迭代器來建立 _AnyIterator 實例:
var iterator = _AnyIterator(_Iterator(obj: Persion.author))
while let item = iterator.next() {
print(item)
}
// out put:
// name is jewelz
// age is 23
// email is hujewelz@gmail.com
複製代碼
咱們但願外面傳入一個閉包也能建立一個 _AnyIterator,如今咱們添加下面的代碼:
init(_ impl: @escaping () -> Element?) {
nextImpl = impl
}
複製代碼
添加這個初始化方法其實爲了方便後面實現 _AnySequence 用的。上面說過 _AnySequence 有個屬性存儲了迭代器的實現,因此咱們的 _AnyIterator 能經過一個閉包來初始化。
_AnyIterator 實現完後就能夠來實現咱們的 _AnySequence 了。我這裏直接給出代碼,同窗們能夠本身去實現:
struct _AnySequence<Element> {
typealias Iterator = _AnyIterator<Element>
private var iteratorImpl: () -> Element?
}
extension _AnySequence: Sequence {
init<S>(_ base: S) where Element == S.Iterator.Element, S: Sequence {
var iterator = base.makeIterator()
iteratorImpl = {
iterator.next()
}
}
func makeIterator() -> _AnyIterator<Element> {
return _AnyIterator(iteratorImpl)
}
}
複製代碼
_AnySequence 的指定構造器也被定義爲泛型,接受一個遵循 Sequence 協議的任何序列做爲參數,而且規定了這個序列的迭代器的 next() 的返回類型要跟咱們定義的這個泛型結構的 Element 類型要一致。這裏的這個泛型約束其實就是咱們實現類型擦除的魔法所在了。它將具體的序列的類型隱藏了起來,只要序列中的值都是相同的類型就能夠當作同一種類型來使用。就像下面的例子中的 array 就能夠描述爲 "元素類型是 String 的任意序列的集合"。
let array = [_AnySequence(Persion.author), _AnySequence(Demo())]
for obj in array {
print("+-------------------------+")
for item in obj {
print(item)
}
}
// out put:
// name is jewelz
// age is 23
// email is hujewelz@gmail.com
// +-------------------------+
// name is Sequence
// author is Persion(name: "jewelz", age: 23, email: "hujewelz@gmail.com")
複製代碼
得益於 Swift 的類型推斷,這裏的 array 能夠不用顯式地指明其類型,點擊 option 鍵,你會發現它是 [_AnySequence<String>]
類型。也就是說只有其元素是 String 的任意序列均可以做爲數組的元素。這就跟咱們平時使用相似 "一個 Int 類型的數組" 的語義是一致的了。若是要向數組中插入一個新元素,能夠這樣建立一個序列:
let s = _AnySequence { () -> _AnyIterator<String> in
return _AnyIterator { () -> String? in
return arc4random() % 10 == 5 ? nil : String(Int(arc4random() % 10))
}
}
array.append(s)
複製代碼
上面的代碼中經過一個閉包初始化了一個 _AnySequence,這裏我就不給出本身的實現,同窗們能夠本身動手實現一下。
在標準庫中,其實已經提供了 AnyIterator 和 AnySequence。我還沒去看標準庫的實現,有興趣的同窗能夠點擊這裏查看。 我這裏實現了本身的 _AnyIterator 和 _AnySequence 就是爲了提供一種實現類型擦除的思路。若是你在項目中頻繁地使用帶有關聯類型或 Self 的協議,那麼你也必定會遇到跟我同樣的問題。這時候實現一個類型擦除的封裝,將具體的類型隱藏了起來,你就不用爲 Xcode 的報錯而抓狂了。