迭代器模式是一种行为模式,它提供了一种循环遍历集合的标准方法。这种模式涉及两种类型:
Swift IteratorProtocol 定义了一种可以使用 for in 循环进行迭代的类型。
迭代器对象是你要使其可迭代的类型。但是,你可以遵循 Sequence,而不是直接遵循 IteratorProtocol,它本身遵循 IteratorProtocol。通过这样做,你将免费获得许多高阶函数,包括地图、过滤器等。
“免费”是什么意思?这意味着这些有用的内置函数可以在任何符合 Sequence 的对象上使用,这样可以避免你编写自己的排序、拆分和比较算法。
如果你不熟悉这些功能,请访问 http://bit.ly/sequence-protocol 了解更多信息。
当你有一个包含一组对象的类型并且你希望使用标准 for in 语法使它们可迭代时,请使用迭代器模式。
打开 Starter 目录中的 IntermediateDesignPattern.xcworkspace,或者从上一章中你自己的 Playground 工作区继续,然后打开 Iterator 页面。
在此示例中,你将创建一个队列。
引用 Swift 算法俱乐部 (http://bit.ly/swift-algorithm-club),
队列是一个列表,你只能在其中插入新项目并从前面删除项目。这确保了第一个你入队的项目也是你出队的第一个项目。先到先得!
在代码示例之后添加以下内容:
你已经定义了 Queue 将包含一个任意类型的数组。
队列的头部将是数组中第一个元素的索引。
有一个 isEmpty bool 来检查队列是否为空。
你给了 Queue 一个计数。
你已经创建了一个用于将元素添加到队列的入队函数。
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(_:) 中添加以下代码:
这是逐行工作的方式:
如果开关打开,则将过滤器设置为 Filter.starRating(atLeast: 4.0)。这只会显示评级为 4.0 或更高的咖啡店。
如果开关没有打开,则将过滤器设置为 Filter.identity()。这将显示所有咖啡店。
在任何一种情况下,你都将 filter.businesses 设置为先前获取的现有业务。
最后,调用 addAnnotations() 来更新地图。
每当你更新地图时,你还需要实际使用过滤器。将 addAnnotations() 的内容替换为以下内容:
这是它的工作原理:
首先从地图中删除现有的注释。这可以防止显示重复项,这是可能的,因为每当用户切换开关时都会调用此方法。
你在过滤器中遍历每个业务。在底层,它调用了过滤器上的 makeIterator(),它调用了 filterBusinesses().makeIterator,这就是过滤业务的实际。
你为每个业务创建一个 viewModel 并将其添加到地图中。
构建并运行应用程序,并尝试切换右上角的开关几次。当它打开时,只会显示评价很高的咖啡店。关闭时,将显示所有咖啡店。
你在本章中学习了迭代器模式。以下是它的关键点:
• 迭代器模式提供了一种使用 for in 语法循环遍历集合的标准方法。
• 最好让你的自定义对象符合Sequence,而不是直接使用IteratorProtocol。
• 通过符合Sequence,你将免费获得map 和filter 等高阶函数。
在过去的几章中,你为 Coffee Question 添加了许多很棒的功能!不过, 你还是有更多可以添加的功能:
• 高级过滤和搜索选项
• 自定义地址输入,而不是仅仅搜索附近
• 保存和显示最喜欢的咖啡店
这些每一项都可以使用你目前所学的现有模式。随心所欲地继续构建 Coffee Quest。
准备好后,继续下一章了解原型设计模式并构建一个新的示例应用程序。
上一章 | 目录 | 下一章 |
---|