闪电阅读#1:Swift中的懒惰收藏

我打算暂时就Swift的日常实验写一些关于我的发现的“简短”文章……🕵

这是第一篇闪电文章! 🚀

问题:地图,flatMap和过滤器功能随附的中间杂波

假设我们在一个假想项目中具有以下要求:

可以下载6帧格式为animation_.jpeg

–使用Request对象下载动画帧。

–丢弃第二帧以获得更好的性能。

在现实世界中,我们很可能会有更多的帧,但可以将其视为简化示例。

我们将不使用这6帧的for循环,而是使用mapflatMapfilter方法。

  让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个中间数组,这绝对没有用。 发生这种情况是因为filtermapflatMap是渴望立即计算的函数。

对于较小的数据集来说这不是问题,但是在处理庞大的数据集时可能会出现问题。

解决方案:使用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循环相同的调用顺序。 因此请记住,为此目的使用数组初始化器可能无法达到预期的性能。