从 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 的报错而抓狂了。

相关文章
相关标签/搜索