如果有這樣的一個需求,我希望能像陣列一樣,用 for 迴圈遍歷一個類或結構體中的所有屬性。就像下面這樣:
let persion = Persion()
for i in persion {
print(i)
}
複製程式碼
要實現這樣的需求,我們需要讓自定義的型別遵守 Sequence 協議。
序列
Sequence 協議是集合型別結構中的基礎。一個序列 (sequence) 代表的是一系列具有相同型別的值,你可以對這些值進行迭代。Sequence 協議提供了許多強大的功能,滿足該協議的型別都可以直接使用這些功能。上面這樣步進式的迭代元素的能力看起來十分簡單,但它卻是 Sequence 可以提供這些強大功能的基礎。
滿足 Sequence 協議的要求十分簡單,你需要做的所有事情就是提供一個返回迭代器 (iterator) 的 makeIterator()
方法:
public protocol Sequence {
associatedtype Iterator : IteratorProtocol
public func makeIterator() -> Self.Iterator
// ...
}
複製程式碼
在 Sequence 協議有個關聯型別 Iterator,而且它必須遵守 IteratorProtocol 協議。從這裡我們可以看出 Sequence 是一個可以建立迭代器協議的型別。所以在搞清楚它的步進式的迭代元素能力之前,有必要了解一下迭代器是什麼。
迭代器
序列通過建立一個迭代器來提供對元素的訪問。迭代器每次產生一個序列的值,並且當遍歷序列時對遍歷狀態進行管理。在 IteratorProtocol 協議中唯一的一個方法是 next(),這個方法需要在每次被呼叫時返回序列中的下一個值。當序列被耗盡時,next() 應該返回 nil,不然迭代器就會一直工作下去,直到資源被耗盡為止。
IteratorProtocol 的定義非常簡單:
public protocol IteratorProtocol {
associatedtype Element
public mutating func next() -> Self.Element?
}
複製程式碼
關聯型別 Element 指定了迭代器產生的值的型別。這裡next()
被標記了 mutating,表明了迭代器是可以存在可變的狀態的。這裡的 mutating 也不是必須的,如果你的迭代器返回的值並沒有改變迭代器本身,那麼沒有 mutating 也是沒有任何問題的。 不過幾乎所有有意義的迭代器都會要求可變狀態,這樣它們才能夠管理在序列中的當前位置。
對 Sequence 和 IteratorProtocol 有了基礎瞭解後,要實現開頭提到的需求就很簡單了。比如我想迭代輸出一個 Person 例項的所有屬性,我們可以這樣做:
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 的報錯而抓狂了。