從 Swift 中的序列到類型擦除

若是有這樣的一個需求,我但願能像數組同樣,用 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)"
    }
}
複製代碼

迭代器中的 childrenAnyCollection<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,這裏我就不給出本身的實現,同窗們能夠本身動手實現一下。

寫在最後

在標準庫中,其實已經提供了 AnyIteratorAnySequence。我還沒去看標準庫的實現,有興趣的同窗能夠點擊這裏查看。 我這裏實現了本身的 _AnyIterator 和 _AnySequence 就是爲了提供一種實現類型擦除的思路。若是你在項目中頻繁地使用帶有關聯類型或 Self 的協議,那麼你也必定會遇到跟我同樣的問題。這時候實現一個類型擦除的封裝,將具體的類型隱藏了起來,你就不用爲 Xcode 的報錯而抓狂了。

相關文章
相關標籤/搜索