4

前言

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

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

// 使用初始结果进行 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 操作符,你可以写一个像这样的函数,详细代码如下:

func sumAllElements(of numbers: [Int]) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}

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

func sumAllElements(of numbers: [Int]) -> Int {
    numbers.reduce(0) { $0 + $1 }
}

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

func sumAllElements(of numbers: [Int]) -> Int {
    numbers.reduce(0, +)
}

使用初始结果进行 reduce

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

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

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

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,代码如下:

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 操作符的功能和应用的见解,使他们能够编写更高效、更优雅的代码。


Swift社区
11.9k 声望3.8k 粉丝

我们希望做一个最专业最权威的 Swift 中文社区,我们希望更多的人学习和使用Swift。我们会分享以 Swift 实战、SwiftUI、Swift 基础为核心的技术干货,欢迎您的关注与支持。