第12章:键值观察¶
应对变化是Combine的核心。发布者允许您订阅它们来处理异步事件。在前面的章节中,您学习了assign(to:on:)它使您能够在发布者每次发布者发出新值时更新对象属性的值。
但是,观察单个变量变化的机制呢?
围绕以下几个选项组合船舶:
- 它为符合
KVO(键值观察)的对象的任何属性提供了一个发布者。 ObservableObject协议处理多个变量可能更改的情况。
介绍publisher(for:options:)¶
KVO一直是Objective-C的重要组成部分。Foundation、UIKit和AppKit类中的大量属性都符合KVO标准。因此,您可以使用KVO机器观察它们的变化。
很容易观察到符合KVO的属性。以下是使用OperationQueue(来自Foundation的类)的示例:
let queue = OperationQueue()
let subscription = queue.publisher(for: \.operationCount)
.sink {
print("Outstanding operations in queue: \($0)")
}
每次您将新操作添加到队列中时,其operationCount都会增加,您的接收器都会收到新计数。当队列消耗了操作时,计数会减少,并且您的接收器再次收到更新的计数。
还有许多其他框架类暴露了符合KVO的属性。只需使用具有KVO合规属性的密钥路径的publisher(for:)和voilà!您会得到一个能够发出价值变化的发布者。您将在本章后面了解有关此选项的更多信息以及可用选项。
Note
Apple不会在其整个框架中提供符合KVO的属性的中央列表。每个类的文档通常指示哪些属性符合KVO。但有时文档可能很稀疏,您只能在文档中找到一些属性的快速注释,甚至在系统头本身中找到。
准备和订阅您自己的符合KVO的属性¶
您还可以在自己的代码中使用键值观察,前提是:
- 您的对象是类(不是结构),并且符合
NSObject, - 您标记属性,以便使用
@objc dynamic属性进行可观察。
完成后,您标记的对象和属性将符合KVO,可以使用Combin进行观察!
Note
虽然Swift语言不直接支持KVO,但标记您的属性@objc dynamic迫使编译器生成触发KVO机器的隐藏方法。描述这台机器超出了本书的范围。只需说,该机器严重依赖NSObject协议中的特定方法,这解释了为什么您的对象需要符合它。
在Playground尝试一个例子:
// 1
class TestObject: NSObject {
// 2
@objc dynamic var integerProperty: Int = 0
}
let obj = TestObject()
// 3
let subscription = obj.publisher(for: \.integerProperty)
.sink {
print("integerProperty changes to \($0)")
}
// 4
obj.integerProperty = 100
obj.integerProperty = 200
在上述代码中,您:
- 创建一个符合
NSObject协议的类。这是KVO所必需的。 - 将您想要可观察的任何属性标记为
@objc dynamic。 - 创建并订阅观察
obj的integerProperty属性的发布者。 - 更新几次属性。
在Playground运行此代码时,您能猜出调试控制台显示的内容吗?
您可能会感到惊讶,但以下是您获得的显示器:
integerProperty changes to 0
integerProperty changes to 100
integerProperty changes to 200
您首先获得integerProperty的初始值,即0,然后收到两个更改。如果您对这个初始值不感兴趣,您可以避免它——继续阅读以了解如何!
您是否注意到,在TestObject中,您正在使用普通的Swift类型(Int),而Objective-C功能的KVO仍然有效?KVO适用于任何Objective-C类型和任何与Objective-C相连的Swift类型。这包括所有原生Swift类型以及数组和词典,前提是它们的值都可以桥接到Objective-C。
试试看!向TestObject添加更多属性:
@objc dynamic var stringProperty: String = ""
@objc dynamic var arrayProperty: [Float] = []
以及对其发布者的订阅:
let subscription2 = obj.publisher(for: \.stringProperty)
.sink {
print("stringProperty changes to \($0)")
}
let subscription3 = obj.publisher(for: \.arrayProperty)
.sink {
print("arrayProperty changes to \($0)")
}
最后,一些财产变化:
obj.stringProperty = "Hello"
obj.arrayProperty = [1.0]
obj.stringProperty = "World"
obj.arrayProperty = [1.0, 2.0]
您会在调试区域中看到初始值和更改。很好!
不过,如果您使用没有与Objective-C相连的纯Swift类型,您将开始遇到麻烦:
struct PureSwift {
let a: (Int, Bool)
}
然后,将属性添加到TestObject:
@objc dynamic var structProperty: PureSwift = .init(a: (0,false))
您会立即在Xcode中看到一个错误,指出“属性无法标记为@objc,因为它的类型无法在Objective-C中表示。”在这里,您达到了键值观察的极限。
Note
观察系统框架对象的变化时要小心。确保文档提到属性是可观察的,因为您无法仅通过查看系统对象的属性列表来获取线索。Foundation、UIKit、AppKit 等都是如此。从历史上看,财产必须具有“KVO意识”才能被观察到。
观察选项¶
您调用的观察更改的方法的完整签名是publisher(for:options:) options参数是一个包含四个值的选项集:.initial、.prior、.old和.new。默认值是[.initial],这就是为什么您看到发布者在发出任何更改之前发出初始值。以下是选项的细目:
.initial发出初始值。.prior当发生更改时,同时发出以前的和新的值。.old和.new在这个发布者中没有使用,他们都什么都不做(只是让新价值通过)。
如果您不想要初始值,您可以简单地写以下内容:
obj.publisher(for: \.stringProperty, options: [])
如果您指定.prior,每次发生更改时,您将获得两个单独的值。修改integerProperty示例:
let subscription = obj.publisher(for: \.integerProperty, options: [.prior])
您现在会在integerProperty订阅的调试控制台中看到以下内容:
integerProperty changes to 0
integerProperty changes to 100
integerProperty changes to 100
integerProperty changes to 200
属性首先从0更改为100,因此您将获得两个值:0和100。然后,它从100更改为200,因此您再次获得两个值:100和200。
可观测对象¶
Combine的ObservableObject协议适用于Swift对象,而不仅仅是来自NSObject的对象。它与@Published属性包装器合作,帮助您使用编译器生成的objectWillChange发布者创建类。
它使您免于编写大量样板,并允许创建自我监控自身属性并通知其任何属性何时更改的对象。
这里有一个例子:
class MonitorObject: ObservableObject {
@Published var someProperty = false
@Published var someOtherProperty = ""
}
let object = MonitorObject()
let subscription = object.objectWillChange.sink {
print("object will change")
}
object.someProperty = true
object.someOtherProperty = "Hello world"
ObservableObject协议的一致性使编译器自动生成objectWillChange属性。这是一个ObservableObjectPublisher,它发出Void项,Never失败。
每当对象的@Published变量之一发生变化时,您都会收到objectWillChange触发。不幸的是,你不知道哪些房产实际上发生了变化。这旨在与SwiftUI配合得很好,SwiftUI合并事件以简化屏幕更新。
关键点¶
- 键值观测主要依赖于
Objective-C运行时和NSObject协议的方法。 Apple框架中的许多Objective-C类都提供了一些符合KVO的特性。- 您可以使自己的属性可观察,前提是它们是符合
NSObject的类,并用@objc dynamic属性标记。 - 您还可以遵守
ObservableObject,并将@Published用于您的属性。编译器生成的objectWillChange发布者每次更改@Published属性之一时都会触发(但没有告诉您哪个属性更改)。
接下来去哪?¶
观察很有趣,但分享就是关怀!继续阅读以了解Combine中的资源,以及如何通过共享它们来保存它们!