一组精心准备的类和协议,旨在为继承您的对象类型注入高效、协议驱动的观察者模式行为。从 2.0.0 版本开始,它包括对键控观察者的支持(有关详细信息,请参见下面的用法示例)
选择 File
-> Swift Packages
-> Add Package Dependency
并输入 https://github.com/Flowduino/Observable.git
您可以在您自己的 Package 的 Package.swift
文件中使用 Observable
作为 Package 依赖项
let package = Package(
//...
dependencies: [
.package(
url: "https://github.com/Flowduino/Observable.git",
.upToNextMajor(from: "2.0.0")
),
],
//...
)
从那里,在任何需要它的您的 package 的 targets 中将 Observable
称为“target dependency”。
targets: [
.target(
name: "YourLibrary",
dependencies: [
"Observable",
],
//...
),
//...
]
然后您可以在任何需要它的代码中执行 import Observable
。
以下是 Observable
提供的功能的一些快速简便的用法示例
您可以在自己的类类型中继承 ObservableClass
以提供开箱即用的观察者模式支持。这不仅适用于 SwiftUI View
中带有 @ObservedObject
修饰的变量,而且还适用于您的类之间(例如,服务之间或存储库之间等)。
首先,您需要定义一个协议,描述您的观察者类类型中实现的方法,您的可观察对象类可以调用这些方法。
/// Protocol defining what Methods the Obverable Class can invoke on any Observer
protocol DummyObservable: AnyObject { // It's extremely important that this Protocol be constrained to AnyObject
func onFooChanged(oldValue: String, newValue: String)
func onBarChanged(oldValue: String, newValue: String)
}
注意 - 重要的是我们的协议定义了 AnyObject
一致性约束,如上所示。
现在,我们可以定义我们的可观察对象,它继承自 ObservableClass
/// Class that can be Observed
class Dummy: ObservableClass {
private var _foo: String = "Hello"
public var foo: String {
get {
return _foo
}
set {
// Invoke onFooChanged for all current Observers
withObservers { (observer: DummyObservable) in
observer.onFooChanged(oldValue: _foo, newValue: newValue)
}
_foo = newValue
objectWillChange.send() // This is for the standard ObservableObject behaviour (both are supported together)
}
}
private var _bar: String = "World"
public var bar: String {
get {
return _bar
}
set {
// Invoke onBarChanged for all current Observers
withObservers { (observer: DummyObservable) in
observer.onBarChanged(oldValue: _bar, newValue: newValue)
}
_bar = newValue
objectWillChange.send() // This is for the standard ObservableObject behaviour (both are supported together)
}
}
}
我们现在可以定义一个观察者来向可观察对象注册,确保我们指定它实现了我们的 DummyObservable
协议
class DummyObserver: DummyObservable {
/* Implementations for DummyObservable */
func onFooChanged(oldValue: String, newValue: String) {
print("Foo Changed from \(oldValue) to \(newValue)")
}
func onBarChanged(oldValue: String, newValue: String) {
print("Bar Changed from \(oldValue) to \(newValue)")
}
}
我们现在可以生成一些简单的代码(例如在 Playground 中)将它们组合在一起
// Playground Code to use the above
var observable = Dummy() // This is the Object that we can Observe
var observer = DummyObserver() // This is an Object that will Observe the Observable
observable.addObserver(observer) // This is what registers the Observer with the Observable!
observable.foo = "Test 1"
observable.bar = "Test 2"
ObservableThreadSafeClass
的工作方式与 ObservableClass
完全相同。内部实现只是将 Observer
集合封装在 DispatchSemaphore
之后,并提供一个旋转门机制,以确保即使在执行 withObservers
时,也可以不受阻碍地访问 addObserver
和 removeObserver
。
它的用法与上面在 ObservableClass
中显示的完全相同,只是您将继承 ObservableClass
替换为继承自 ObservableThreadSafeClass
。
ObservableThread
为您提供了一个基本类型,用于您想要观察的任何线程类型。
注意 - ObservableThread
确实实现了 ObservableObject
协议,并且技术上与 SwiftUI View
中的 @ObservedObject
属性装饰器兼容。但是,要以这种方式使用它,在任何您将调用 objectWillUpdate.send()
的地方,您都必须改为使用 notifyChange()
。在内部,ObservableThread
将执行 objectWillChange.send()
但强制它必须在 MainActor
上执行(正如 Swift 所要求的)
现在让我们开始看看如何在您的代码中使用 ObservableThread
。该示例是有意简化的,只是在线程中的无限循环中每 60 秒生成一个随机数。
让我们首先定义我们的观察协议
protocol RandomNumberObserver: AnyObject {
func onRandomNumber(_ randomNumber: Int)
}
我们线程的任何观察者都需要符合上面的 RandomNumberObserver 协议。
现在,让我们定义我们的 RandomNumberObservableThread 类
class RandomNumberObservableThread: ObservableThread {
init() {
self.start() // This will start the thread on creation. You aren't required to do it this way, I'm just choosing to!
}
public override func main() { // We must override this method
while self.isExecuting { // This creates a loop that will continue for as long as the Thread is running!
let randomNumber = Int.random(in: -9000..<9001) // We'll generate a random number between -9000 and +9000
// Now let's notify all of our Observers!
withObservers { (observer: RandomNumberObserver) in
observer.onRandomNumber(randomNumber)
}
Self.sleep(forTimeInterval: 60.00) // This will cause our Thread to sleep for 60 seconds
}
}
}
所以,我们现在有一个可以被观察的线程,并且每当它生成一个随机整数时,每分钟都会通知所有观察者。
现在让我们实现一个旨在观察这个线程的类
class RandomNumberObserverClass: RandomNumberObserver {
public func onRandomNumber(_ randomNumber: Int) {
print("Random Number is: \(randomNumber)")
}
我们现在可以在一个简单的 Playground 中将它们组合在一起
var myThread = RandomNumberObservableThread()
var myObserver = RandomNumberObserverClass()
myThread.addObserver(myObserver)
就这样! Playground 程序现在只需每 60 秒将新的随机数通知消息打印到控制台输出中。
您可以为所需的任何基于观察的线程行为采用这种方法,因为 ObservableThread
将始终在其自己的线程的执行上下文中调用观察者回调方法!这意味着,例如,您可以安全地在 UI 线程上实例化一个观察者类,而正在观察的代码执行驻留在其自己的线程中(每个需求一个或多个)。
从 1.1.0 版本开始,您现在可以注册和通知键控观察者。注意:2.0.0 版本对界面进行了重大修改,消除了对 Key 进行泛型类型化的需求。现在可以为您推断 Key 类型。
此功能是标准观察者模式的扩展,并在您可以扩展的以下类中实现
KeyedObservableClass
而不是 ObservableClass
KeyedObservableThread
而不是 ObservableThread
KeyedObservableThreadSafeClass
而不是 ObservableThreadSafeClass
请记住,键控观察是基本观察模式的扩展,因此任何键控可观察对象本质上都能够注册和通知非键控观察者
每当您的观察者关心特定的更改上下文时,您就会使用键控观察。一个很好的例子是模型存储库,其中观察者可能只关心对存储库中包含的特定模型的更改。在这种情况下,您将使用键控观察来确保仅通知观察者与给定 Key 对应的更改。
Key 类型必须始终符合 Hashable
协议,就像用于 Dictionary
集合的任何 Key 类型一样。
让我们看一个基本用法示例。
我们将提供一个基本用法示例,仅使用来自 Observable 的内部 Dictionary 的值同步观察者的内部 Dictionary 以获取特定键。
首先,我们将从观察协议开始
protocol TestKeyedObservable: AnyObject {
func onValueChanged(key: String, oldValue: String, newValue: String)
}
上面的观察协议提供了方法 onValueChanged
,它采用 key
(在本例中为 String
值),并提供该 key
的相应 oldValue
和 newValue
值。我们的观察者将实现 TestKeyedObservable
以提供此函数的实现。
现在,让我们定义一个简单的键控可观察对象,以容纳我们将有选择地与一个或多个观察者同步的主 Dictionary。
class TestKeyedObservableClass: KeyedObservableClass<String> {
private var keyValues: [String:String] = ["A":"Hello", "B":"Foo", "C":"Ping"]
func setValue(key: String, value: String) {
withKeyedObservers(key: key) { (key, observer: TestKeyedObservable) in
observer.onValueChanged(key: key, oldValue: self.keyValues[key]!, newValue: value)
}
self.keyValues[key] = value
}
}
上面的类继承自 KeyedObservableClass
并专门化 TKey
泛型为 String
。换句话说,此 Observable 的 Key 必须始终是 String
值。它包括一个简单的 String:String
字典(String
键和 String
值)
setValue
方法将简单地使用 withKeyedObservers
通知所有观察者,任何时候观察者正在观察的特定 key
被更新,传递 oldValue
和 newValue
值。然后它将更新其内部 Dictionary (keyValues
),以便它始终包含最新值。
请注意使用 withKeyedObservers
而不是 withObservers
。您将在您自己的键控可观察对象中使用此语法,仅更改声明的观察者协议(在本例中为 TestKeyedObservable
)以及表示您自己的观察方法的观察者协议。
现在我们有一个键控可观察对象,它将在每次键的值更改时通知观察者,让我们定义一个观察者。
class TestKeyedObserverClass: TestKeyedObservable {
public var keyValues: [String:String] = ["A":"Hello", "B":"Foo"]
func onValueChanged(key: String, oldValue: String, newValue: String) {
keyValues[key] = newValue
}
}
因此,TestKeyedObserverClass
是一个简单的类,实现了我们的 TestKeyedObservable
观察者协议。对于此示例,我们假设有 2 个具有已知初始值的预定义 Key(不必如此...您可以拥有任意数量的 Key)
您会注意到我们初始化了 Observable 和 Observer 类以具有相同的 keyValues
字典。这仅仅是为了简化此示例,方法是确保始终存在 oldValue
。您无需在自己的实现中执行此操作。
所以,既然我们有了可观察对象和观察者类型,让我们生成一段简单的 Playground 代码将它们组合在一起。
let observable = TestKeyedObservableClass() // Creates our Observable
let observer = TestKeyedObserverClass // Creates a single Observer instance
此时,我们需要考虑我们的 observer
将要观察哪个或哪些 Key。
例如,我们可以仅观察一个 key
observable.addKeyedObserver(key: "A", observer)
上面的意思是只有当 observable
中修改了 key A 的值时,才会调用 observer
的 onValueChanged
方法。
同样,如果我们只关心 key B,我们可以这样做
observable.addKeyedObserver(key: "B", observer)
如果我们关心两个已知 key,我们可以简单地将它们都注册
observable.addKeyedObserver(keys: ["A", "B"], observer)
此外,我们可以做一些特别聪明的事情,并且基本上为自己的 Dictionary 已知的每个 Key 注册 Observer
observable.addKeyedObserver(keys: Array(observer.keyValues.keys), observer)
上面将为 observer
的 keyValues
字典中包含的每个 key 向 observable
注册 observer
。
最终,您可以为所需的任何 key 向 observable
注册 observer
observable.addKeyedObserver(key: "Foo", observer)
让我们在调用任何会修改其值的代码之前输出我们所有 key 的初始值
for (key, value) in observer.keyValues {
print("Key: '\(key)' has a value of '\(value)'")
}
这将输出
Key: 'A' 的值为 'Hello' Key: 'B' 的值为 'Foo'
所以,既然我们可以为我们想要的任何 key 向 Observer 注册键控观察者,让我们在 observer
中触发观察者模式
observable.setValue(key: "A", "World")
上面会将 A 的值从“Hello”更新为“World”。
如果我们重复以下代码
for (key, value) in observer.keyValues {
print("Key: '\(key)' has a value of '\(value)'")
}
这将输出
Key: 'A' 的值为 'World' Key: 'B' 的值为 'Foo'
好的,如果我们更改 key "C" 的值会怎样?会发生什么?
observable.setValue(key: "C", "Pong")
现在,如果我们重复以下代码
for (key, value) in observer.keyValues {
print("Key: '\(key)' has a value of '\(value)'")
}
这将输出
Key: 'A' 的值为 'World' Key: 'B' 的值为 'Foo'
请注意,没有通知 observer
更改 key C 的值。这是因为 observer
没有观察 observable
是否更改 key C。
这就是键控观察模式的价值。简而言之:并非所有观察对所有观察者都有意义。因此,正如您现在所看到的,键控观察使我们的观察者能够专门收到与该观察者相关的更改的通知。
从 1.1.0 版本开始,已为 ObservableClass
、ObservableThread
、ObservableThreadSafeClass
、KeyedObservableClass
、KeyedObservableThread
和 KeyedObservableThreadSafeClass
的上述指定方法提供所有有用的组合重载,以简化添加和删除带有/来自可观察对象的观察者。
observable.addObserver(myObserver)
observable.addObserver([myObserver1, myObserver2, myObserver3])
keyedObservable.addKeyedObserver("MyKey", myKeyedObserver)
keyedObservable.addKeyedObserver(["Key1", "Key2", "Key3"], myKeyedObserver)
keyedObservable.addKeyedObserver("MyKey", [myKeyedObserver1, myKeyedObserver2, myKeyedObserver3])
keyedObservable.addKeyedObserver(["Key1", "Key2", "Key3"], [myKeyedObserver1, myKeyedObserver2, myKeyedObserver3])
removeObserver
和 removeKeyedObserver
也提供与上面所示相同的重载。
关于此软件包,您应该了解以下一些有用的补充信息。
此库有意对每个注册的 Observer 执行运行时类型检查,以确保它符合您的 withObservers
Closure 方法所明确定义的 Observer Protocol。
简单的示例协议
protocol ObserverProtocolA: AnyObject {
func doSomethingForProtocolA()
}
protocol ObserverProtocolB: AnyObject {
func doSomethingForProtocolB()
}
然后,这两个协议可以被同一个 ObservableClass
、ObservableThreadSafeClass
或 ObservableThread
后代类使用。
withObservers { (observer: ObserverProtocolA) in
observer.doSomethingForProtocolA()
}
withObservers { (observer: ObserverProtocolB) in
observer.doSomethingForProtocolB()
}
任意数量的 Observer Protocol 可以由我们的任何 Observable 类型进行编组,并且只有符合明确指定的 Observer Protocol 的 Observer 会被传递到您的 withObservers
Closure 方法中。
Observable
在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。
如果您需要更多支持,或者想讨论 Observable
、Swift 或任何其他与 Flowduino 相关的主题,可以加入我们的 Discord。