SwiftObserver 是一个轻量级的响应式 Swift 包。它的设计目标是易于学习和使用。
SwiftObserver 只有 1400 行生产代码,但这背后是超过 1000 小时的工作。 自 2013 年的先驱实现以来,它不断地被重新构想、重新设计和经过实战检验, 放弃了许多花哨的功能,同时改进了文档和 单元测试。
响应式编程解决了实现有效架构的核心挑战:控制依赖方向,特别是使特定关注点依赖于抽象的关注点。 SwiftObserver 将响应式编程分解为它的本质,即观察者模式。
SwiftObserver 偏离了惯例,因为它没有继承常见响应式库的隐喻、术语、类型、函数和运算符武器库。 它不如 Rx 和 Combine 那样花哨,也不像 Redux 那样具有限制性。 相反,它提供了一种强大的简洁性,您可能会真正喜欢使用它。
使用Swift Package Manager,您可以通过Xcode (11+) 添加 SwiftObserver 包。
或者您可以手动调整项目的Package.swift文件
// swift-tools-version:5.6.0
import PackageDescription
let package = Package(
name: "MyProject",
platforms: [
.iOS(.v12), .macOS(.v10_14), .tvOS(.v12), .watchOS(.v6)
],
products: [
.library(
name: "MyProject",
targets: ["MyProject"]
)
],
dependencies: [
.package(
url: "https://github.com/codeface-io/SwiftObserver.git",
exact: "7.0.3"
)
],
targets: [
.target(name: "MyProject",
dependencies: ["SwiftObserver"])
]
)
然后运行 $ swift build
或 $ swift run
。
最后,在您的 Swift 文件中
import SwiftObserver
无需学习一堆任意的隐喻、术语或类型。
SwiftObserver 很简单:对象观察其他对象。
或者更技术性地说:可观察对象向他们的观察者发送消息。
就是这样。只是可读的代码
dog.observe(Sky.shared) { color in
// marvel at the sky changing its color
}
任何对象都可以成为一个 Observer
,如果它有一个 Receiver
用于接收消息
class Dog: Observer {
let receiver = Receiver()
}
接收器保持观察者的观察活跃。 观察者只是强烈地抓住它。
Observer
/Receiver
必须仍然存在。 内存中死亡后没有任何感知。Observer
可以对同一个 ObservableObject
进行多次同步观察,例如通过多次调用 observe(...)
。observer.isObserving(observable)
检查 observer
是否正在观察 observable
。任何对象都可以成为一个 ObservableObject
,如果它有一个 Messenger<Message>
用于发送消息
class Sky: ObservableObject {
let messenger = Messenger<Color>() // Message == Color
}
ObservableObject
通过 send(_ message: Message)
发送消息。 对象的客户端,甚至它的观察者,也可以自由地调用该函数。ObservableObject
完全按照调用 send
的顺序传递消息,这有助于观察者从他们的消息处理闭包中以某种方式触发进一步调用 send
。ObservableObject
不会 触发它发送消息。 这使一切保持简单、可预测和一致。Messenger<Message>
。它是一个中介,其他实体通过它进行通信。Messenger<Message>
的自定义 ObservableObject
类的对象。Variable<Value>
(也称为 Var<Value>
)。它保存一个值并发送值更新。ObservableObject
。使用 SwiftObserver,您不必为每次新的观察处理 “Cancellables”、“Tokens”、“DisposeBags” 或任何此类奇怪的东西。 而且,您也不必担心任何特定的内存管理。 当一个 Observer
或 ObservableObject
死亡时,SwiftObserver 会自动清理所有相关的观察。
当然,观察对象和被观察对象仍然可以自由停止特定或所有正在进行的观察
dog.stopObserving(Sky.shared) // no more messages from the sky
dog.stopObserving() // no more messages from anywhere
Sky.shared.stopBeingObserved(by: dog) // no more messages to dog
Sky.shared.stopBeingObserved() // no more messages to anywhere
Messenger
是最简单的 ObservableObject
,也是每个其他 ObservableObject
的基础。 它本身不发送消息,但任何人都可以通过它发送消息,并将其用于任何类型的消息
let textMessenger = Messenger<String>()
observer.observe(textMessenger) { textMessage in
// respond to textMessage
}
textMessenger.send("my message")
Messenger
体现了常见的 信使/通知器模式,并且可以开箱即用。
拥有一个 Messenger
实际上定义了一个 ObservableObject
public protocol ObservableObject: AnyObject {
var messenger: Messenger<Message> { get }
associatedtype Message: Any
}
Messenger
本身就是一个 ObservableObject
,因为它指向自身作为所需的 Messenger
extension Messenger: ObservableObject {
public var messenger: Messenger<Message> { self }
}
每个其他的 ObservableObject
类要么是 Messenger
的子类,要么是提供 Messenger
的自定义 ObservableObject
类。 自定义可观察对象通常采用一些 enum
作为它们的消息类型
class Model: SuperModel, ObservableObject {
func foo() { send(.willUpdate) }
func bar() { send(.didUpdate) }
deinit { send(.willDie) }
let messenger = Messenger<Event>() // Message == Event
enum Event { case willUpdate, didUpdate, willDie }
}
Var<Value>
是一个具有属性 var value: Value
的 ObservableObject
。
每当它的 value
改变时,Var<Value>
发送一个类型为 Update<Value>
的消息,通知 old
和 new
值
let number = Var(42)
observer.observe(number) { update in
let whatsTheBigDifference = update.new - update.old
}
number <- 123 // use convenience operator <- to set number.value
此外,您可以随时手动调用 variable.send()
(不带参数)来发送一个更新,其中 old
和 new
都保存当前 value
(请参阅拉取最新消息)。
属性包装器 ObservableVar
允许直接访问实际的 Value
。 让我们将它应用于上面的示例
@ObservableVar var number = 42
observer.observe($number) { update in
let whatsTheBigDifference = update.new - update.old
}
number = 123
包装器的 projected value 提供底层的 Var<Value>
,您可以通过 $
符号访问它,就像上面的示例中一样。 这类似于您如何在 Combine 中访问 @Published
属性的底层发布者。
如果 Var<Value>
的 Value
是 Codable
,则 Var<Value>
会自动变为 Codable
。 因此,当您的类型之一具有 Var
属性时,您可以通过简单地采用 Codable
协议来使该类型成为 Codable
class Model: Codable {
private(set) var text = Var("String Variable")
}
请注意,text
是一个 var
而不是一个 let
。 它不能是常量,因为 Swift 的隐式解码器必须对其进行变异。 但是,Model
的客户端应该只设置 text.value
而不是 text
本身,因此 setter 是私有的。
转换使消息处理的常见步骤更加简洁和可读。 它们允许以多种方式映射、过滤和解包消息。 您可以自由地将这些转换链接在一起,并使用它们定义新的转换。
此示例将类型为 Update<String?>
的消息转换为类型为 Int
的消息
let title = Var<String?>()
observer.observe(title).new().unwrap("Untitled").map({ $0.count }) { titleLength in
// do something with the new title length
}
您可以像在上面的示例中一样,直接动态地转换特定的观察结果。 这种特殊的转换赋予观察者很大的灵活性。
或者您可以实例化一个新的 ObservableObject
,它具有内置的转换链。 上面的例子可以写成这样
let title = Var<String?>()
let titleLength = title.new().unwrap("Untitled").map { $0.count }
observer.observe(titleLength) { titleLength in
// do something with the new title length
}
每个转换对象都将其底层的 ObservableObject
公开为 origin
。 您甚至可以替换 origin
let titleLength = Var("Dummy Title").new().map { $0.count }
let title = Var("Real Title")
titleLength.origin.origin = title
这种独立的转换可以为多个观察者提供相同的预处理。 但是由于这些转换是不同的 ObservableObject
,您必须在某处强烈地持有它们。 将转换链作为专用的可观察对象持有,适合于诸如视图模型之类的实体,这些实体表示其他数据的转换。
无论您是以临时方式还是作为独立对象应用转换,它们的工作方式都相同。以下列表展示了作为可观察对象的预构建转换。
首先,是您熟悉的 map
函数。它转换消息,通常也转换消息的类型。
let messenger = Messenger<String>() // sends String
let stringToInt = messenger.map { Int($0) } // sends Int?
当像 Var<Value>
这样的 ObservableObject
发送 Update<Value>
类型的消息时,我们通常只关心 new
值,因此我们使用 new()
映射更新。
let errorCode = Var<Int>() // sends Update<Int>
let newErrorCode = errorCode.new() // sends Int
如果您只想接收某些特定的消息,请使用 filter
。
let messenger = Messenger<String>() // sends String
let shortMessages = messenger.filter { $0.count < 10 } // sends String if length < 10
使用 select
仅接收一个特定消息。select
适用于所有 Equatable
消息类型。select
将消息类型映射到 Void
,因此选择后的接收闭包不接受任何消息参数。
let messenger = Messenger<String>() // sends String
let myNotifier = messenger.select("my notification") // sends Void (no messages)
observer.observe(myNotifier) { // no argument
// someone sent "my notification"
}
有时,我们将消息类型设置为可选类型,例如当 Var
没有有意义的初始值时。但是我们通常不想在后续操作中处理可选类型。因此,我们可以使用 unwrap()
,完全抑制 nil
消息。
let errorCodes = Messenger<Int?>() // sends Int?
let errorAlert = errorCodes.unwrap() // sends Int if the message is not nil
您还可以通过将 nil
值替换为默认值来解包可选消息。
let points = Messenger<Int?>() // sends Int?
let pointsToShow = points.unwrap(0) // sends Int with 0 for nil
您可以将多个转换链接在一起。
let numbers = Messenger<Int>()
observer.observe(numbers).map {
"\($0)" // Int -> String
}.filter {
$0.count > 1 // suppress single digit integers
}.map {
Int.init($0) // String -> Int?
}.unwrap { // Int? -> Int
print($0) // receive and process resulting Int
}
当然,像上面这样的临时转换最终会落在实际的消息处理闭包上。现在,当链中的最后一个转换也需要一个闭包参数进行处理时,比如 map
和 filter
,我们使用 receive
来保持 尾随闭包 的良好语法。
dog.observe(Sky.shared).map {
$0 == .blue
}.receive {
print("Will we go outside? \($0 ? "Yes" : "No")!")
}
CombineObserver 是 SwiftObserver 包的另一个库产品。它依赖于 SwiftObserver,并提供了一种简单的方法将任何 SwiftObserver-ObservableObject
转换为 Combine-Publisher
。
import CombineObserver
@ObservableVar var number = 7 // SwiftObserver
let numberPublisher = $number.publisher() // Combine
let cancellable = numberPublisher.dropFirst().sink { numberUpdate in
print("\(numberUpdate.new)")
}
number = 42 // prints "42"
这种互操作只有一个方向。以下是其背后的原因:SwiftObserver 适用于没有外部依赖的纯 Swift-/模型代码 – 甚至不依赖于 Combine。当与 Combine 结合使用时(哎呀),SwiftObserver 将被用于应用程序的模型核心,而 Combine 将更多地用于 I/O 外围设备,例如 SwiftUI 和其他已经依赖 Combine 的系统特定 API。这意味着,“Combine 层”可能希望观察(对…做出反应)“SwiftObserver 层” – 但反之则不然。
ObservableCache
是一个 ObservableObject
,它具有一个属性 latestMessage: Message
,通常返回最后发送的消息或指示没有任何更改的消息。 ObservableCache
有一个函数 send()
,它不接受任何参数并发送 latestMessage
。
任何 Var
都是 ObservableCache
。它的 latestMessage
是一个 Update
,其中 old
和 new
都持有当前的 value
。
自定义的可观察对象可以轻松地符合 ObservableCache
。即使它们的消息类型不是基于某种状态,latestMessage
仍然可以返回有意义的默认值 - 甚至在 Message
是可选类型时返回 nil
。
在 ObservableObject
上调用 cache()
会创建一个 转换,该转换是一个 ObservableCache
。该缓存的 Message
将是可选的,但永远不会是可选的可选,即使源的 Message
已经是可选的。
当然,cache()
作为观察的临时转换没有意义,因此它只能创建一个不同的可观察对象。
如果任何转换的来源是 ObservableCache
,并且它从不抑制(过滤)消息,则该转换本身隐式地是一个 ObservableCache
。这些兼容的转换是:map
、new
和 unwrap(default)
。
请注意,作为隐式 ObservableCache
的转换的 latestMessage
返回其底层 ObservableCache
源的转换后的 latestMessage
。在该转换本身上调用 send(transformedMessage)
不会“更新”其 latestMessage
。
像 Var
这样的 ObservableObject
从其状态派生消息,可以按需生成“最新消息”,因此可以充当 ObservableCache
。
class Model: Messenger<String>, ObservableCache { // informs about the latest state
var latestMessage: String { state } // ... either on demand
var state = "initial state" {
didSet {
if state != oldValue {
send(state) // ... or when the state changes
}
}
}
}
每个消息都有一个与之关联的作者。只有当您使用它时,此功能才会体现在代码中。
可观察对象可以通过 object.send(message, from: author)
将作者与消息一起发送。如果没人指定作者,如 object.send(message)
中那样,则可观察对象本身将成为作者。
变量有一个特殊的 setter,允许识别更改作者。
let number = Var(0)
number.set(42, as: controller) // controller becomes author of the update message
观察者可以通过将其作为参数添加到消息处理闭包中来接收作者。
observer.observe(observableObject) { message, author in
// process message from author
}
通过作者,观察者可以确定消息的来源。在普通的信使模式中,来源将只是消息发送者。
当多个观察者观察同一对象,而他们的行为可能导致该对象发送消息时,识别消息作者变得至关重要。
可变数据是这种共享可观察对象的常见类型。例如,当多个实体观察和修改存储抽象或缓存层次结构时,它们通常希望避免对自己的行为做出反应。这种过度反应可能导致冗余工作或无限响应循环。因此,它们在修改数据时将自己标识为更改作者,并在观察数据时忽略来自 self
的消息。
class Collaborator: Observer {
func observeText() {
observe(sharedText).notFrom(self) { update, author in // see author filters below
// someone else edited the text
}
}
func editText() {
sharedText.set("my new text", as: self) // identify as change author
}
let receiver = Receiver()
}
let sharedText = Var<String>()
有三种与消息作者相关的转换。与其他转换一样,我们可以直接在观察中应用它们,或者将它们创建为独立的可观察对象。
我们像过滤消息一样过滤作者。
let messenger = Messenger<String>() // sends String
let friendMessages = messenger.filterAuthor { // sends String if message is from friend
friends.contains($0)
}
如果只对一个特定作者感兴趣,请使用 from
过滤作者。它以弱引用的方式捕获选定的作者。
let messenger = Messenger<String>() // sends String
let joesMessages = messenger.from(joe) // sends String if message is from joe
如果对**除了一个之外的所有**特定作者感兴趣,请使用 notFrom
。它也以弱引用的方式捕获排除的作者。
let messenger = Messenger<String>() // sends String
let humanMessages = messenger.notFrom(hal9000) // sends String, but not from an evil AI
当您想将 ObservableObject
放入某个数据结构中,或者作为转换对象中的来源,但希望以 weak
引用的方式持有它时,请通过 observableObject.weak()
对其进行转换。
let number = Var(12)
let weakNumber = number.weak()
observer.observe(weakNumber) { update in
// process update of type Update<Int>
}
var weakNumbers = [Weak<Var<Int>>]()
weakNumbers.append(weakNumber)
当然,weak()
作为临时转换没有意义,因此它只能创建一个不同的可观察对象。
这是“SwiftObserver”目标的内部架构(组合和基本依赖关系)。
更多顶级源文件夹的图表在此处。这些图像是使用 Codeface 生成的。