什么时候使用 forEach(_:) 而不是 for in?

When to use forEach(_:) instead of for in?

Array and Dictionary forEach(_:) 实例方法中所述:

Calls the given closure on each element in the sequence in the same order as a for-in loop.

尽管如此,改编自Sequence Overview

A sequence is a list of values that you can step through one at a time. The most common way to iterate over the elements of a sequence is to use a for-in loop.

暗示 forEach(_:)for in 的迭代序列:

let closedRange = 1...3

for element in closedRange { print(element) } // 1 2 3

closedRange.forEach { print([=10=]) } // 1 2 3

或(数组):

let array = [1, 2, 3]

for element in array { print(element) } // 1 2 3

array.forEach { print([=11=]) } // 1 2 3

会给出相同的输出。

为什么 forEach(_:) 还存在?即使用它而不是 for in 循环有什么好处?从性能的角度来看它们是否相同?

作为一个假设,它可能是一种语法糖,尤其是在使用 函数式编程时

forEach 没有提供任何性能优势。事实上,if you look at the source codeforEach函数实际上只是执行for-in。对于发布版本,此函数的性能开销比简单地使用 for-in 本身是无关紧要的,但对于调试版本,它会导致可观察到的性能影响。

forEach 的主要优势在您进行函数式编程时体现出来,您可以将其添加到函数调用链中,而无需将先前的结果保存到您需要的单独变量中如果您使用 for-in 语法。所以,而不是:

let objects = array.map { ... }
    .filter { ... }

for object in objects {
    ...
}

您可以改用函数式编程模式:

array.map { ... }
    .filter { ... }
    .forEach { ... }

结果是功能代码更简洁,语法噪音更少。

FWIW,Array, Dictionary, and Sequence 的文档都提醒我们 forEach 引入的限制,即:

  1. You cannot use a break or continue statement to exit the current call of the body closure or skip subsequent calls.

  2. Using the return statement in the body closure will exit only from the current call to body, not from any outer scope, and won't skip subsequent calls.

它们或多或少可以互换,但有两个重要区别。

  1. break/continue 仅适用于 for .. in
  2. return in forEach 将退出闭包,但不会停止迭代。

原因是 for .. in 是语言中的一种 特殊形式 (允许中断并继续按预期工作)。这是您无法使用语言本身以相同方式实现的东西。

但是,forEach 不是特殊形式,可以通过将其编写为函数来重新实现。

extension Sequence {
    func myOwnForEach(_ body: (Self.Element) throws -> Void) rethrows {
        let it = Self.makeIterator()
        while let item = it.next() {
            body(item)
        }
    }
}

我最近 运行 研究了一个用例,其中使用 forEach 在有形的方式上比 for in 更可取。假设您要从图层中删除所有子图层。像下面这样的语句不起作用,因为您需要打开 [CALayer]

for layer in self.videoContainerView.layer.sublayers!

如果子层为零,您将遇到崩溃。这会强制您首先检查是否有子层。但是,forEach 使这变得更简单,如下所示:

self.videoContainerView.layer.sublayers?.forEach { [=11=].removeFromSuperlayer() }

除了上述答案之外,将 for 循环与 forEach 区分开来的另一个原因是,对于 for 循环,我们还可以选择使用 where 来实现该逻辑基于模式匹配,比如

for adBanner in adBanners where !adBanner.isLoading {

上述与控制流相关的特性是 for 循环如此强大的原因,但如果我们不需要那种级别的控制,则使用对 forEach 的调用可能会让我们的代码看起来稍微简单一些。

简而言之,使用 for 循环可以让我们更好地控制迭代