若是有这样的一个需求,我但愿能像数组同样,用 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 的报错而抓狂了。