这里每天分享一个 iOS 的新知识,快来关注我吧
前言
我相信大多数人听过惰性集合(lazy collections),但是却很少用到。惰性集合与常规集合功能类似,但它改变了 map、 filter 和 reduce 等高阶函数的处理方式。在某些情况下利用惰性集合可以提高应用的性能。
今天的文章主要来介绍一下如何使用惰性集合。
lazy collection
惰性集合也是集合的一种,只不过它在进行某些计算的时候会推迟进行,比如数组的遍历取值时,它往往不会直接遍历所有的元素,而是直到真正用到某个元素的时候才会执行,这在许多不同的情况下都是有益的,并且可以防止很多不必的性能开销。
我们先来举个例子,有一个数字集合,我需要先把偶数筛选出来,再把筛选出的结果乘以 2,最终得到结果。正常的代码是这样的:
var numbers: [Int] = [1, 2, 3, 4]
let modifiedNumbers = numbers
.filter { number in
print("执行了 filter")
return number % 2 == 0
}.map { number -> Int in
print("执行了 map")
return number * 2
}
print(modifiedNumbers)
// 打印结果
//执行了 filter
//执行了 filter
//执行了 filter
//执行了 filter
//执行了 map
//执行了 map
//[4, 8]
执行的结果符合我们的预期,先把 numbers 每个元素走了一遍 filter,过滤出偶数(2 和 4),然后再用 map 函数把过滤出的两个数乘 2,最终结果是 [4, 8]。
接下来我们用惰性集合重写这块代码,使用惰性集合只需要在集合后加上 .lazy 关键字:
let modifiedNumbers = numbers
.lazy
.filter { number in
print("执行了 filter")
return number % 2 == 0
}.map { number -> Int in
print("执行了 map")
return number * 2
}
print(modifiedNumbers)
// 打印结果
//LazyMapSequence<LazyFilterSequence<Array<Int>>, Int>(_base: Swift.LazyFilterSequence<Swift.Array<Swift.Int>>(_base: [1, 2, 3, 4], _predicate: (Function)), _transform: (Function))
从结果可以看出,最终打印了一个 LazyMapSequence<LazyFilterSequence<Array<Int>> 对象,而 filter 和 map 的循环一次都没有走进去。原因正是我们上边提到的,使用 lazy 之后只有真正用到某个元素的时候才会执行。
我们尝试打印结果中的第一个值:
print(modifiedNumbers.first)
// 打印结果
//执行了 filter
//执行了 filter
//执行了 map
//Optional(4)
从打印结果可以看出,当我们获取第一个值的时候,先走了两次 filter,找出第一个符合条件的数字 2,然后走了一次 map 把最终结果算出来 4。如果我们仅仅是想获取结果中的第一个值,那么使用 lazy 的性能要比不使用 lazy 好的多,尤其是数组非常大的时候。
什么时候应该使用惰性集合
也不是所有情况都适合使用惰性集合,比如当你的数组中元素不多时,其实用不用惰性集合性能都不会有差别,理论上来说只有当你集合非常大,而你又只需要使用一小部分数据(不需要每个都计算一遍)的时候使用惰性集合比较合适。
还有一种情况是当计算的下游还有其他函数时,它会把上游每一个计算出的值优先走下游的函数,比如你要过滤出 0 - 100000000 之间所有的偶数,并乘以 2,这是很慢的过程。而 lazy 的行为是每次计算出一个偶数就会执行一下乘以 2 的操作,而不是把所有值计算完成后再乘 2,适用于边计算边出结果的情况。
还有一点需要注意,惰性集合计算出的结果并不会缓存,就以我们上边的代码为例,你每次调用 modifiedNumbers.first 都会执行两次 filter 和一次 map。这时候如果你想重复利用计算出的值,就需要你先把结果缓存起来,下次使用直接使用缓存值,避免重新计算一次。
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!