惰性序列 是对集合的特殊封装,它允许将计算(如 filter、map 等)的执行推迟到实际访问元素之时。这意味着对惰性序列的操作将在结果被访问时才会计算(例如,在调用 .forEach、.first、转换为数组等时)。
何时使用:
示例:
let numbers = Array(1...1_000_000) let lazyNumbers = numbers.lazy.map { $0 * 2 }.filter { $0 % 3 == 0 } let first = lazyNumbers.first // 只有在这里,针对第一个匹配元素的操作链才会被计算!
潜在的陷阱:
.map { ... }和.lazy.map { ... }在数组上的调用有什么区别?
答案:
.map { ... } 将闭包应用于每个元素并立即返回一个新的数组,也就是说所有元素会被处理并保存在内存中。.lazy.map { ... } 返回的不是数组,而是惰性序列(封装),它不会立即处理元素,而是会在访问它们时才进行处理。示例:
let a = Array(1...10) let eagers = a.map { $0 * 2 } // 包含 10 个元素的数组 let laziers = a.lazy.map { $0 * 2 } // 不立即包含结果的 LazySequence
故事
在一个大型项目中,开发人员对一个庞大的数据数组使用了多个 map、filter、reduce 的链式调用,而没有使用 .lazy。这导致每个中间步骤临时分配了大量数组,几乎将内存消耗增加了一倍,并在一些低内存设备上导致崩溃。
故事
在代码块中,使用了带有副作用的惰性序列(例如,map/filter 内的事件发送或打印)。开发人员期望此操作会立即执行,但事件根本没有发生——因为从未访问惰性序列的元素,包含事件的代码根本没有被调用。因此,日志和指标出现了不正确的情况。
故事
在从大型数据库收集统计数据时,使用了惰性序列,并结合多次遍历(例如,寻找 first 两次,然后计算 count)。每次遍历惰性序列都会启动完整的操作重新计算——这导致系统的延迟加倍,并增加了不必要的负载。在将其替换为普通数组后,问题消失了。