掌握 Swift 中的 reduce 操作符,使你的代码更高

前言

Swift 的 Sequence 类型有一个强大的操作符叫做 reduce,它允许你将序列的所有元素组合成一个单一的值。在处理来自 App Store Connect API 的响应时,我一直在反复使用它,我觉得写一篇关于它的博客文章是个好主意。

reduce 操作符有两种不同的签名,详细代码如下:

1
2
3
4
5
swift复制代码// 使用初始结果进行 reduce
@inlinable public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (_ partialResult: Result, Self.Element) throws -> Result) rethrows -> Result

// 将结果归并到初始结果中
@inlinable public func reduce<Result>(into initialResult: Result, _ updateAccumulatingResult: (_ partialResult: inout Result, Self.Element) throws -> ()) rethrows -> Result

这两个操作符在给定相同输入时实现相同的结果:它们从一个初始的 inout 值开始,遍历序列中的所有元素,并将它们作为参数传递给提供的闭包。由于初始值是作为 inout 参数传递的,闭包可以根据序列中的当前元素对其进行修改。每次迭代的更新值然后作为下一次迭代中闭包的第一个参数传递。

虽然它们看起来非常相似 - 它们都具有 O(n) 的复杂度,并且可以互换使用 - 但基于结果类型的不同,它们具有不同的效率影响。例如,当结果是像 ArrayDictionary 这样的写时复制类型时,你应该使用 into 变体。

使用初始结果进行 reduce

让我们来看一个非常简单的例子,以理解 reduce 操作符的工作原理。假设你有一个整数数组,你想要计算所有元素的总和作为结果。如果你不知道 reduce 操作符,你可以写一个像这样的函数,详细代码如下:

1
2
3
4
5
6
7
swift复制代码func sumAllElements(of numbers: [Int]) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}

虽然这个函数完全有效,但它并不是最优雅的解决方案。你可以在一行代码中使用 reduce 操作符来实现相同的结果,代码如下:

1
2
3
swift复制代码func sumAllElements(of numbers: [Int]) -> Int {
numbers.reduce(0) { $0 + $1 }
}

或者更好的是,你可以直接将 + 操作符作为闭包传递,代码如下:

1
2
3
swift复制代码func sumAllElements(of numbers: [Int]) -> Int {
numbers.reduce(0, +)
}

使用初始结果进行 reduce

现在让我们来看一个稍微复杂一些的例子。假设我们有一个 ScreenshotBundle 数组,其中每个 bundle 都有一个名称和一个指向截图的 URL 列表。我们的 UI 需要根据用户的选择找到具有特定名称的截图 bundle,并在图像视图中显示所有的 URL

这是我们在 Helm 中使用的代码变体,Hidde 和我正在构建 Helm,这是一款旨在使 App Store Connect 的用户更轻松、更愉快地发布应用程序和更新的应用。

我们可以通过保持 ScreenshotBundle 数组不变,然后搜索具有特定名称的 bundle 来实现这一点,核心代码如下:

1
2
3
4
5
6
7
8
swift复制代码struct ScreenshotBundle {
let name: String
let urls: [URL]
}

func find(bundleWithName name: String, in bundles: [ScreenshotBundle]) -> ScreenshotBundle? {
bundles.first(where: { $0.name == name })
}

虽然这种方法可行,但它并不是最有效的。first(where:) 函数的复杂度为 O(n),你可以想象,如果数组中的元素数量很大,这可能会成为一个问题。

相反,你可以使用 reduce 操作符一次将 ScreenshotBundle 数组转换为一个字典,其中键是 bundle 的名称,值是 bundle 本身。这样,你就可以在 O(1) 的时间复杂度内找到具有特定名称的 bundle,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
swift复制代码struct ScreenshotBundle {
let name: String
let urls: [URL]
}

func format(bundles: [ScreenshotBundle]) -> [String: ScreenshotBundle] {
bundles.reduce(into: [String: ScreenshotBundle]()) { result, bundle in
result[bundle.name] = bundle
}
}

func find(bundleWithName name: String, in bundles: [String: ScreenshotBundle]) -> ScreenshotBundle? {
bundles[name]
}

通过理解和掌握 reduce 操作符,你可以更高效地处理 Swift 中的集合类型,使你的代码更加简洁和易于理解。这种强大的操作符不仅能够提高代码的性能,还能提升开发效率,让你更轻松地应对复杂的数据处理任务。

在实际开发中,应该根据具体情况选择合适的 reduce 操作符,以确保代码的性能和可读性。通过合理地利用 reduce 操作符,你可以编写出更加优雅和高效的 Swift 代码,从而提升应用程序的质量和用户体验。

了解 reduce 操作符的工作原理并熟练运用它,将会使你成为一个更加出色的 Swift 开发者,为你的项目带来更大的成功和成就。

总结

本文全面介绍了 Swift 中的 reduce 操作符,这是一个强大的工具,可以将序列的元素组合成单个值。文章解释了 reduce 操作符的两种不同签名,并通过代码示例演示了它们的用法。

其中讨论了如何使用带有初始结果的 reduce,演示了如何以简洁而优雅的方式计算数组中元素的总和。然后,它探讨了带有初始结果的 reduce 变体,展示了如何将数组高效地转换为字典。

本文对 Swift 开发人员来说是一份宝贵的资源,提供了关于 reduce 操作符的功能和应用的见解,使他们能够编写更高效、更优雅的代码。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%