观察者模式让一个对象观察另一个对象的变化。苹果在Swift 5.1中增加了对这种模式的语言级支持,在Combine框架中增加了Publisher。
这种模式涉及三种类型。
订阅者是 "观察者 "对象,接收更新。
发布者是 "可观察 "对象并发送更新。
值是被改变的基础对象。
只要你想接收另一个对象上的变化,就可以使用观察者模式。
这种模式经常被用于MVC,其中视图控制器有订阅者,模型有发布者。这允许模型将变化传达给视图控制器,而不需要知道任何关于视图控制器的类型。因此,不同的视图控制器可以使用和观察同一模型类型的变化。
打开Starter目录下的FundamentalDesignPattern.xcworkspace,或者从上一章中你自己的playground工作区继续,然后打开Overview页面。
你会看到Observer被列在Behavioral Patterns下面。这是因为观察者是关于一个物体观察另一个物体的。
点击Observer链接,打开该页面。
然后,输入下面的代码示例。
以下是你的做法。
首先,你导入Combine,它包括@Published注释和Publisher & Subscriber类型。
接下来,你声明一个新的用户类;@Published属性不能用于结构体或除类之外的任何其他类型。
接下来,你为name创建一个var属性,并将其标记为@Published。这告诉Xcode为这个属性自动生成一个Publisher。注意,你不能对let属性使用@Published,因为根据定义,它们不能被改变。
最后,你要创建一个初始化器来设置self.name的初始值。接下来,在playground的末尾添加以下代码。
以下是你的做法。
首先,你创建了一个名为Ray的新用户。
接下来,你通过user.$name访问发布者,以广播该用户的名字的变化。这将返回一个类型为Posted
接下来,你通过调用发布者的sink来创建一个订阅者。这需要一个闭包,在初始值和值发生变化时调用。默认情况下,sink返回一个AnyCancellable类型。然而,你明确地将这个类型声明为AnyCancellable? 以使其成为可选的,因为你以后会将其清空。
最后,你把用户的名字改为Vicki。
作为回应,你应该看到以下内容被打印到控制台。
User's name is Ray
User's name is Vicki
在后面添加如下代码:
subscriber = nil
user.name = "Ray has left the building"
通过将订阅者设置为nil,它将不再接收来自发布者的更新。为了证明这一点,你最后一次改变了用户的名字,但你不会在控制台中看到任何新的输出。
在你实现观察者模式之前,先确定你期望改变的内容和条件。如果你不能确定一个对象或属性改变的原因,你最好不要把它声明为var或@Published,而应该把它变成一个let属性。
例如,一个唯一的标识符,作为一个公布的属性是没有用的,因为从定义上来说,它不应该改变。
你将继续上一章的Rabble Wabble应用程序。
如果你跳过了上一章,或者你想重新开始,请打开Finder并导航到你下载本章资源的地方。然后,在Xcode中打开starter ▸ RabbleWabble ▸ RabbleWabble.xcodeproj。
你将使用观察者模式在 "选择题组 "屏幕上显示用户的最新分数。
从文件层次结构中打开QuestionGroup.swift。这已经有了一个分数,但目前还不能观察它的变化。在导入Foundation下面添加以下内容。
import Combine
这就导入了苹果的新Combine框架,为你完成了所有繁重的工作。
接下来,在Score类(位于QuestionGroup类内)的末尾,在其其他属性之后添加以下内容,暂时忽略编译器错误。
@Published public var runningPercentage: Double = 0
runningPercentage属性将允许观察该问题组的最新 "运行百分比得分"。
编译器目前正在抛出一个错误,因为该属性被标记为@Published,而且它不知道如何自动编码或解码它。要解决这个问题,请在Score的init之后添加以下代码。
下面是这个的作用。
你首先声明一个CodingKeys的枚举以及correctCount和incorrectCount的情况。这告诉编译器在它自动生成的编码器和解码器方法中忽略runningPercentage。
在一个Score被解码的情况下,你需要实际设置runningPercentage。要做到这一点,你要为init(from decoder:)创建一个自定义的初始化器,并在设置correctCount和incorrectCount后调用updateRunningPercentage()。
在updateRunningPercentage()中,你根据correctCount与totalCount的比率来设置runningPercentage。
接下来,将var correctCount和var incorrectCount两行替换为以下内容。
虽然你可以将correctCount和incorrectCount标记为@Published,但你对单独观察这些属性不感兴趣。相反,你感兴趣的是它们如何影响runningPercentage。所以在didSet中,你为每个属性调用updateRunningPercentage()。
在你开始为runningPercentage创建订阅者之前,你需要做一些小改动。首先,在Score类的结尾大括号前添加以下方法。
虽然你可以将correctCount和incorrectCount标记为@Published,但你对单独观察这些属性不感兴趣。相反,你感兴趣的是它们如何影响runningPercentage。所以在didSet中,你为每个属性调用updateRunningPercentage()。
在你开始为runningPercentage创建订阅者之前,你需要做一些小改动。首先,在Score类的结尾大括号前添加以下方法。
public func reset() {
correctCount = 0
incorrectCount = 0
}
这个方法 "重置 "了Score。你将在用户重新启动一个问题组时使用它。
接下来,将var score一行替换为以下内容,忽略由此产生的编译器错误。
public private(set) var score: Score
这可以防止所有外部类直接设置分数。这可以确保任何runningPercentage订阅者不会意外地被抹去,如果直接设置分数,就会发生这种情况。
目前有一个地方可以直接设置分数。打开BaseQuestionStrategy.swift,替换下面一行。
self.questionGroupCaretaker.selectedQuestionGroup.score =
QuestionGroup.Score()
使用如下代码:
self.questionGroupCaretaker.selectedQuestionGroup.score.reset()
构建并运行以确保你没有任何编译器错误。到目前为止,似乎没有什么变化,但你现在已经准备好注册你的观察者了
你首先需要一个地方来保存订阅者对象。理想情况下,这应该与它相关的对象的生命联系在一起。在这种情况下,这就是QuestionGroupCell本身。打开QuestionGroupCell.swift,添加以下内容 import UIKit:
import Combine
接下来,在其他属性之后添加以下属性。
public var percentageSubscriber: AnyCancellable?
然后,打开SelectQuestionGroupViewController.swift,在tableView(_:cellForRowAt:)中加入以下代码,就在返回语句之前。
以下是其作用。
将cell.percentSubscriber设置为所创建的订阅者。因此,如果单元格被释放,它的订阅者也会自动被释放,它将不会收到更新。
调用receive(on:)并传递DispatchQueue.main以确保事件被传递到主队列。虽然目前在应用程序中没有使用任何后台线程,但最好总是确保你的UI调用是在主队列上进行的,以防止将来出现问题。
使用地图将数值转换为百分比字符串。
调用assign将值设置为cell.percentLabel上的文本。每当数值发生变化,这也会自动更新标签的文本。
建立并运行,选择任何你想要的问题组单元格,并点击几次 "正确 "和 "不正确 "按钮。当你按下 "菜单 "按钮时,分数就会显现出来。更好的是,如果你退出应用程序并重新启动,由于你实现了上一章的纪念品模式,分数将被持续保留。
你在本章中了解了观察者模式。下面是它的关键点。
观察者模式让一个对象观察另一个对象的变化。它涉及三种类型:订阅者、发布者和值。
订阅者是观察者;发布者是可观察的对象;而值是被改变的对象。
Swift 5.1使得使用@Published属性来实现观察者模式变得容易。
RabbleWabble的功能正变得越来越丰富。然而,有一个功能将是非常棒的:用户能够创建他们自己的问题组。你将使用另一种模式来实现这个功能:构建器设计模式。
继续看下一章,了解建造者模式并完成RabbleWabble应用程序。
上一章 | 目录 | 下一章 |
---|