第13章:迭代器模式

迭代器模式是一种行为模式,它提供了一种循环遍历集合的标准方法。这种模式涉及两种类型:

  1. Swift IteratorProtocol 定义了一种可以使用 for in 循环进行迭代的类型。

  2. 迭代器对象是你要使其可迭代的类型。但是,你可以遵循 Sequence,而不是直接遵循 IteratorProtocol,它本身遵循 IteratorProtocol。通过这样做,你将免费获得许多高阶函数,包括地图、过滤器等。

“免费”是什么意思?这意味着这些有用的内置函数可以在任何符合 Sequence 的对象上使用,这样可以避免你编写自己的排序、拆分和比较算法。

如果你不熟悉这些功能,请访问 http://bit.ly/sequence-protocol 了解更多信息。

你应该什么时候使用它?

当你有一个包含一组对象的类型并且你希望使用标准 for in 语法使它们可迭代时,请使用迭代器模式。

Playground示例

打开 Starter 目录中的 IntermediateDesignPattern.xcworkspace,或者从上一章中你自己的 Playground 工作区继续,然后打开 Iterator 页面。

在此示例中,你将创建一个队列。

引用 Swift 算法俱乐部 (http://bit.ly/swift-algorithm-club),
队列是一个列表,你只能在其中插入新项目并从前面删除项目。这确保了第一个你入队的项目也是你出队的第一个项目。先到先得!

在代码示例之后添加以下内容:

在这里,你创建了一个包含数组的队列。以下是代码的细分:
  1. 你已经定义了 Queue 将包含一个任意类型的数组。

  2. 队列的头部将是数组中第一个元素的索引。

  3. 有一个 isEmpty bool 来检查队列是否为空。

  4. 你给了 Queue 一个计数。

  5. 你已经创建了一个用于将元素添加到队列的入队函数。

  6. dequeue 函数用于移除队列的第一个元素。设置此函数的逻辑是为了帮助你避免在数组中包含 nil 对象。接下来,将以下代码添加到 Playground 的末尾以测试队列:

队列有四个项目,一旦你成功地将第一张票从队列中取出,就会变成三个。

在真实的用例场景中,你肯定希望能够按优先级对这些工单进行排序。按照现在的情况,你需要编写一个包含大量 if 语句的排序函数。节省一些时间,改用 Swift 的内置排序功能之一。

目前,如果你尝试在队列中使用 for in 循环或 sorted(),你会得到一个错误。你需要使你的 Queue 结构符合 Sequence 协议。在 Queue 结构下添加以下内容:

与 dequeue 一样,你要确保不暴露 nil 对象,并且只遍历非空值。

符合 Sequence 协议时需要两个部分。第一个是你的关联类型,即你的迭代器。在上面的代码中,IndexingIterator 是你的关联类型,它是任何未声明自己的集合的默认迭代器。

第二部分是Iterator协议,也就是需要的makeIterator函数。它为你的类或结构构造一个迭代器。

将以下内容添加到文件底部:

这将遍历你的票并打印它们。

在使用特定于序列的排序函数之前,请向上滚动并在 Ticket 结构下添加以下扩展:

将数值分配给优先级将使排序更容易。使用他们的 sortIndex 作为参考对票进行排序,在文件末尾添加以下代码:

排序函数返回一个常规数组,因此要获得排序队列,请将每个数组项排队到新队列中。如此轻松地对组进行排序的能力是一项强大的功能,并且随着列表和队列变得越来越大,它变得更加有价值。

你应该注意什么?

有一个名为IteratorProtocol的协议,它允许你自定义对象的迭代方式。你只需实现一个 next() 方法,该方法在迭代中返回下一个对象。但是,你可能永远不需要直接遵守IteratorProtocol。

即使需要自定义迭代器,也几乎总是最好符合 Sequence 并提供自定义 next() 逻辑,而不是直接遵循 IteratorProtocol。

你可以在 http://bit.ly/iterator-protocol 找到有关IteratorProtocol及其如何与Sequence配合使用的更多信息。

教程项目

你将继续构建上一章中的咖啡任务。你最终将在右上角为交换机添加功能!

如果你跳过了上一章,或者想要重新开始,请打开 Finder 并导航到你下载本章资源的位置。然后,在 Xcode 中打开 starter\CoffeeQuest\CoffeeQuest.xcworkspace (not .xcodeproj!)。

注意:如果你选择重新开始,那么你需要打开 APIKeys.swift 并添加你的 Yelp API 密钥。有关如何生成它的说明,请参阅第 10 章,“模型-视图-视图模型模式”。

转到 Models 组并选择 File ▸ New ▸ File... 并选择 Swift File。将新文件命名为 Filter.swift。通过在 import Foundation 下添加以下代码来创建一个 Filter 结构:

该结构包含一个业务对象数组和一个过滤器闭包。你可以使用 identity() 实例化该类,使用 starRating() 调整过滤器的参数,并使用 filterBusinesses() 应用过滤器。

你还可以通过扩展使过滤器符合序列,其中你从 filterBusinesses() 的返回值创建一个迭代器。

设置好过滤器包装后,你现在可以在 ViewController 中使用此逻辑。打开 ViewController.swift。将以下代码行添加到顶部的属性列表中:

private var filter = Filter.identity()

这将为过滤器创建一个新属性并将其默认值设置为 Filter.identity()。

接下来,在 searchForBusinesses 函数中,在 self.businesses = searchResult.businesses 行下方和 DispatchQueue.main.async 行上方添加以下内容:

self.filter.businesses = businesses

在这里,你将 filter.businesses 设置为获取的业务。

接下来,你将根据右上角的开关设置过滤器。在 businessFilterToggleChanged(_:) 中添加以下代码:

这是逐行工作的方式:

  1. 如果开关打开,则将过滤器设置为 Filter.starRating(atLeast: 4.0)。这只会显示评级为 4.0 或更高的咖啡店。

  2. 如果开关没有打开,则将过滤器设置为 Filter.identity()。这将显示所有咖啡店。

  3. 在任何一种情况下,你都将 filter.businesses 设置为先前获取的现有业务。

  4. 最后,调用 addAnnotations() 来更新地图。

每当你更新地图时,你还需要实际使用过滤器。将 addAnnotations() 的内容替换为以下内容:

这是它的工作原理:

  1. 首先从地图中删除现有的注释。这可以防止显示重复项,这是可能的,因为每当用户切换开关时都会调用此方法。

  2. 你在过滤器中遍历每个业务。在底层,它调用了过滤器上的 makeIterator(),它调用了 filterBusinesses().makeIterator,这就是过滤业务的实际。

  3. 你为每个业务创建一个 viewModel 并将其添加到地图中。

构建并运行应用程序,并尝试切换右上角的开关几次。当它打开时,只会显示评价很高的咖啡店。关闭时,将显示所有咖啡店。

关键点

你在本章中学习了迭代器模式。以下是它的关键点:

• 迭代器模式提供了一种使用 for in 语法循环遍历集合的标准方法。

• 最好让你的自定义对象符合Sequence,而不是直接使用IteratorProtocol。

• 通过符合Sequence,你将免费获得map 和filter 等高阶函数。

然后去哪儿?

在过去的几章中,你为 Coffee Question 添加了许多很棒的功能!不过, 你还是有更多可以添加的功能:

• 高级过滤和搜索选项

• 自定义地址输入,而不是仅仅搜索附近

• 保存和显示最喜欢的咖啡店

这些每一项都可以使用你目前所学的现有模式。随心所欲地继续构建 Coffee Quest。

准备好后,继续下一章了解原型设计模式并构建一个新的示例应用程序。


上一章 目录 下一章