闪电阅读#1:Swift中的懒惰收藏
我打算暂时就Swift的日常实验写一些关于我的发现的“简短”文章……🕵
这是第一篇闪电文章! 🚀
问题:地图,flatMap和过滤器功能随附的中间杂波
假设我们在一个假想项目中具有以下要求:
可以下载6帧格式为
animation_.jpeg
。
–使用
Request
对象下载动画帧。
–丢弃第二帧以获得更好的性能。
在现实世界中,我们很可能会有更多的帧,但可以将其视为简化示例。
我们将不使用这6帧的for循环,而是使用map
, flatMap
和filter
方法。
让frameIndexes:[Int] = Array(1 ... 6)
让imageRequests:[请求] = frameIndexes
//通过每隔一帧过滤进行优化:
.filter {$ 0%2 == 1}
//将帧号转换为图像名称:
.map {“ animation _ \($ 0).jpeg”}
//将图片名称转换为网址:
.flatMap {URL(string:“ https://www.somehost.com/\($0)”)}
//转换网址以请求对象:
.map {Request(url:$ 0)}
这比具有for循环更具可读性。 但是性能如何? 让我们逐行遍历此代码块来详细研究。
-
filter
块对数组中的每个帧索引运行6次。 - 使用3个过滤的帧索引创建一个新数组。
- 每个图块运行3次。
- 将使用3个图像名称创建一个新数组。
-
flatMap
块对每个项目运行3次。 - 将使用3个URL创建一个新数组。 (假设将在每个图像名称上成功创建URL。)
-
map
块在每个URL上运行3次。 - 使用3个请求对象创建一个新数组。
呼叫顺序将如下所示:
过滤
过滤
过滤
过滤
过滤
过滤
//创建一个新数组。
mapToName
mapToName
mapToName
//创建一个新数组。
flatMapToURL
flatMapToURL
flatMapToURL
//创建一个新数组。
mapToRequest
mapToRequest
mapToRequest
//创建一个新数组。
尽管我们只需要最后的结果数组,但我们又创建了3个中间数组,这绝对没有用。 发生这种情况是因为filter
, map
和flatMap
是渴望立即计算的函数。
对于较小的数据集来说这不是问题,但是在处理庞大的数据集时可能会出现问题。
解决方案:使用LazyCollection
使用一个惰性集合,我们可以摆脱中间的数组创建。 Array
符合在RandomAccessCollection
上定义的lazy
属性。 lazy
使用LazyRandomAccessCollection
类型基本上提供了同一数组的惰性实现,该类型仅在需要时才计算值。
让我们现在实现惰性方法。
让frameIndexes:[Int] = Array(1 ... 6)
让lazyImageRequests = frameIndexes
。懒
.filter {$ 0%2 == 1}
.map {“ animation _ \($ 0).jpeg”}
.flatMap {URL(string:“ https://www.somehost.com/\($0)”)}
.map {Request(url:$ 0)}
容易吧? 我们保持将值从一个转换为另一个的声明性方法,但是仅在.lazy
之后添加.lazy
达到良好的性能。
它将执行得更好,因为除非访问元素,否则什么也不会执行。 此时,我们有2个选项:
- 我们可以像常规数组一样使用惰性集合,并在访问它们时计算每个值。
- 我们可以逐一遍历惰性集合的元素,然后将它们存储在普通数组中。
我们将使用第二个选项来检查呼叫顺序。
var imageRequests:[请求] = []
在lazyImageRequests中请求
imageRequests.append(请求)
}
//我们还可以使用:
// lazyImageRequests.forEach {imageRequests.append($ 0)}
在此for循环之后,调用顺序为:
//项目0:
过滤
//项目1:
过滤
mapToName
flatMapToURL
mapToRequest
//项目2:
过滤
//项目3:
过滤
mapToName
flatMapToURL
mapToRequest
//项目4:
过滤
//项目5:
过滤
mapToName
flatMapToURL
mapToRequest
底线
如果我们不使用懒惰;
- 我们遍历原始数组4次。
- 我们创建3个冗余中间阵列。
如果我们使用懒惰的话;
- 我们只迭代一次。
- 在访问它们时,我们通过管道获得了每个计算出的元素。
关于缓存的注意事项
请注意,如果我们再处理lazyImageRequests
,则将再次计算每个项目。 因此,如果您计划多次访问这些项目,则建议将它们缓存在传统数组中,就像我们在上面的示例中所做的一样。
关于数组转换的注意事项
我们还可以使用数组初始化器将惰性数组转换为普通数组,如下所示。
let imageRequests:[请求] = Array(lazyImageRequests)
我不确定延迟集合的内部原理,但是出于某种原因,上面的代码无法打印出与for循环相同的调用顺序。 因此请记住,为此目的使用数组初始化器可能无法达到预期的性能。