在Swift中创建对象的列表和序列时,大多数时候我们使用数组数据结构。虽然数组在易于使用方面有很大的好处,而且地球上的每个程序员或多或少都知道这一点,但它们确实要求您预先创建所有元素。
当处理较小的数据集时,每个成员的构造成本不是很高,这不是问题。 然而,如果这不是真的,那么您可以通过实现自己的延迟计算序列而不是使用数组来获得一些相当大的性能好处。
假设我们想从本地数据库加载一系列模型:
class ModelLoader {
func loadAllModels() -> [Model] { … }
}
我们的数据库可能包含大量的记录,对于每个模型,我们都需要访问磁盘才能实际加载它的数据,所以我们不希望一次加载所有的数据。为了实现这一点,我们将用我们自己的自定义序列替换数组返回类型。
由于Swift面向协议的特性,定义自己的序列非常容易。它还——多亏了协议扩展——让您可以访问标准库提供的序列(如Array、Set或Dictionary)上使用的所有api,而无需为其编写任何代码。
我们首先为序列创建一个结构体,并使其符合序列协议:
struct ModelSequence: Sequence {
func makeIterator() -> ModelIterator {
return ModelIterator()
}
}
如您所见,要符合序列,我们所需要做的就是能够充当创建迭代器的工厂。Swift实际上使用迭代器来遍历序列,就像在for循环或forEach()调用中一样。
对于我们的迭代器,我们会一直从磁盘加载模型,直到找不到模型为止,在这种情况下,我们会返回nil。 从迭代器的next()方法返回nil向Swift发送序列已经结束的信号,迭代将停止。
用数据库初始化迭代器,我们将使用该数据库加载每个模型。我们默认使用数据库的共享实例,但是为了方便测试,我们也启用了它的依赖注入。
struct ModelIterator: IteratorProtocol {
private let database: Database
private var index = 0
init(database: Database = .shared) {
self.database = database
}
mutating func next() -> Model? {
let model = database.model(at: index)
index += 1
return model
}
}
就是这样!现在我们有了一个惰性评估序列,在需要时加载每个模型🎉。 现在,只要想要遍历数据库中的所有模型,我们就可以轻松地使用序列。 例如,我们现在可以搜索我们的数据库,而不必预先加载所有的记录:
func searchForModel(matching query: String) -> Model? {
for model in ModelSequence() {
if model.title.contains(query) {
return model
}
}
return nil
}
上面代码的好处在于,一旦找到匹配项,我们就可以通过返回退出迭代,从而防止进一步的数据库记录被加载。
好了,奖金环节到了!正如我们刚刚看到的,在Swift中实现您自己的自定义序列和迭代器非常容易。但是当你真的很懒的时候,标准库的AnySequence类型有一个基于闭包的API,你可以用它来快速实现简单的序列,像这样:
class ModelLoader {
func loadAllModels() -> AnySequence<Model> {
return AnySequence { () -> AnyIterator<Model> in
var index = 0
return AnyIterator {
let model = database.model(at: index)
index += 1
return model
}
}
}
}
在我的代码中,我个人已经开始在很多不同的情况下使用自定义序列。我发现,在上述情况下,它不仅性能更好,而且更容易调试,因为您可以简单地一步一步地遍历迭代代码以发现任何问题。
你怎么看?你觉得在Swift中定义序列的能力有用吗?