第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
中的资源,以及如何通过共享它们来保存它们!