Pharos 是一个 Swift 的观察者模式框架,它利用了 propertyWrapper
。在使用响应式编程设计应用程序时,它可以提供很大的帮助。 在底层,它使用 Chary 作为 DispatchQueue 的实用工具。
要运行示例项目,请克隆该仓库,并首先从 Example 目录运行 pod install
。
Pharos 可通过 CocoaPods 获得。 要安装它,只需将以下行添加到你的 Podfile 中
pod 'Pharos'
在你的 Package.swift 中添加作为目标依赖项
dependencies: [
.package(url: "https://github.com/hainayanda/Pharos.git", .upToNextMajor(from: "4.0.0"))
]
在你的目标中将其用作 Pharos
.target(
name: "MyModule",
dependencies: ["Pharos"]
)
Nayanda Haberty, hainayanda@outlook.com
Pharos 在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。
你所需要的只是一个你想观察的属性,并为其添加 @Subject
propertyWrapper。
class MyClass {
@Subject var text: String?
}
要观察文本中发生的任何更改,请使用其 projectedValue
来获取其 `Observable`,并传递闭包订阅者。
class MyClass {
@Subject var text: String?
func observeText() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}.retain()
}
}
每次文本中发生任何设置时,它都会调用带有其更改的闭包,其中包括旧值和新值。 只要该值为 Equatable
,你就可以忽略任何不更改值的设置。
class MyClass {
@Subject var text: String?
func observeText() {
$text.distinct()
.observeChange { changes in
print(changes.new)
print(changes.old)
}.retain()
}
}
如果你希望观察者使用当前值运行,只需触发它即可。
class MyClass {
@Subject var text: String?
func observeText() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}.retain()
.fire()
}
}
如果你想忽略观察旧值,请使用 observe
代替。
class MyClass {
@Subject var text: String?
func observeText() {
$text.observe { newValue in
print(newValue)
}.retain()
}
}
你始终可以通过访问 observable 属性来检查当前值。
class MyClass {
@Subject var text: String = "my text"
func printCurrentText() {
print(text)
}
}
默认情况下,如果你观察 Observable 并以 retain()
结束,则闭包将由 Observable 本身保留。 如果 Observable 被 ARC
删除,它将自动被 ARC
删除。 如果你想使用自定义对象保留闭包,你可以这样做
class MyClass {
@Subject var text: String?
func observeText() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retained(by: self)
}
}
在上面的示例中,闭包将由 MyClass
实例保留,如果该实例被 ARC 删除,则将被删除。
如果你想手动处理保留,你始终可以使用 Retainer
来保留观察者
class MyClass {
@Subject var text: String?
var retainer: Retainer = .init()
func observeText() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retained(by: retainer)
}
func discardManually() {
retainer.discardAllRetained()
}
func discardByCreateNewRetainer() {
retainer = .init()
}
}
有很多方法可以丢弃由 Retainer
管理的订阅者
discardAllRetained()
ARC
从内存中删除 retainer,因此默认情况下将丢弃其所有管理的订阅者。ARC
丢弃,它将自动丢弃 Retainer
,因此默认情况下将丢弃其所有管理的订阅者。你可以随时使用各种 retain 方法来控制你想要保留多长时间。
class MyClass {
@Subject var text: String?
var retainer: Retainer = .init()
func observeTextOnce() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retainUntilNextState()
}
func observeTextTenTimes() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retainUntil(nextEventCount: 10)
}
func observeTextForOneMinutes() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retain(for: 60)
}
func observeTextUntilFoundMatches() {
$text.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retainUntil {
$0.new == "found!"
}
}
}
明智地使用此 retain 功能,因为如果你不了解 ARC 的工作原理,它可能会引入 retain cycle。
只要在 iOS 中,你就可以通过调用 observeEventChange
来观察 UIControl
中的事件,或者如果你想观察特定事件,或者更具体地说是 touchUpInside 事件,则可以使用 whenDidTriggered(by:)
,或 whenDidTapped
。
myButton.observeEventChange { changes in
print("new event: \(changes.new) form old event: \(changes.old)")
}.retain()
myButton.whenDidTriggered(by: .touchDown) { _ in
print("someone touch down on this button")
}.retain()
myButton.whenDidTapped { _ in
print("someone touch up on this button")
}.retain()
你可以通过访问 bindables
中的 observables 来观察受支持的 UIView
属性中的更改。
class MyClass {
var textField: UITextField = .init()
func observeText() {
textField.bindables.text.observeChange { changes in
print(changes.new)
print(changes.old)
}.retain()
}
}
你始终可以绑定两个 Observables 以相互通知
class MyClass {
var textField: UITextField = .init()
@Subject var text: String?
func observeText() {
$text.bind(with: textField.bindables.text)
.retain()
}
}
在上面的示例中,每次设置 text
时,它都会自动设置 textField.text
,并且当设置 textField.text
时,它会自动设置 text
。
你可以通过传递一个返回 Bool
值的闭包来过滤值,该值指示应忽略该值。
class MyClass {
@Subject var text: String
func observeText() {
$text.ignore { $0.isEmpty }
.observeChange { changes in
print(changes.new)
print(changes.old)
}.retain()
}
}
在上面的示例中,当新值为空时,observeChange
闭包将不会运行。
与 ignore
相反的是 filter
class MyClass {
@Subject var text: String
func observeText() {
$text.filter { $0.count > 5 }
.observeChange { changes in
print(changes.new)
print(changes.old)
}.retain()
}
}
在上面的示例中,仅当新值大于 5 时,observeChange 闭包才会运行。
有时你只是想延迟一些观察,因为如果值来的太快,它可能会成为你的一些业务逻辑的瓶颈,例如当你调用 API 或其他操作时。 它会在闭包触发时自动使用最新值。
class MyClass {
@Subject var text: String?
func observeText() {
$text.throttled(by: 1)
.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retain()
}
func test() {
text = "this will trigger the observable and block observer for next 1 second"
text = "this will be stored in pending but will be replaced by next set"
text = "this will be stored in pending but will be replaced by next set too"
text = "this will be stored in pending and be used at next 1 second"
}
}
你可以添加 DispatchQueue
以确保你的 observable 在正确的线程上运行。 如果未提供 DispatchQueue
,它将使用来自通知程序的线程。
class MyClass {
@Subject var text: String?
func observeText() {
$text.dispatch(on: .main)
.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retain()
}
}
它将使在此 dispatch 调用之后的所有订阅者都在给定的 DispatchQueue
中异步运行。
如果它已经在同一个 DispatchQueue
中,你可以通过使用 observe(on:)
使其同步。
class MyClass {
@Subject var text: String?
func observeText() {
$text.observe(on: .main)
.observeChange { changes in
print(changes.new)
print(changes.old)
}
.retain()
}
}
你可以通过使用映射将 Subject
中的值映射到另一种类型。 映射将创建一个具有映射类型的新 Observable。
class MyClass {
@Subject var text: String
func observeText() {
$text.mapped { $0.count }
.observeChange { changes in
print("text character count is \(changes.new)")
}.retain()
}
}
你始终可以在映射期间映射并忽略错误或 nil。 成功映射后始终会调用 Did set 闭包。
class MyClass {
@Subject var text: String?
func observeText() {
$text.compactMapped { $0?.count }
.observeChange { changes in
// this will not run if text is nil
print("text character count is \(changes.new)")
}.retain()
}
}
你始终可以使用 ObservableBlock
从代码块创建 Observable
let myObservableFromBlock = ObservableBlock { accept in
runSomethingAsync { result in
accept(result)
}
}
myObservableFromBlock.observeChange { changes in
print(changes)
}.retain()
publisher 是仅用于发布值的 Observable。
let myPublisher = Publisher<Int>()
...
...
// it will then publish 10 to all of its subscribers
myPublisher.publish(10)
你可以将来自任何 Observable 的值 relay 到另一个 Observable。
class MyClass {
@Subject var text: String?
@Subject var count: Int = 0
@Subject var empty: Bool = true
func observeText() {
$text.compactMap { $0?.count }
.relayChanges(to: $count)
.retain()
}
}
你始终可以通过访问 relayables
将值 relay 到任何 NSObject Bearer Observables。 它使用 dynamicMemberLookup
,因此所有对象的可写属性都将在此处可用。
class MyClass {
var label: UILabel = UILabel()
@Subject var text: String?
func observeText() {
$text.relayChanges(to: label.relayables.text)
.retain()
}
}
你可以合并尽可能多的 observables,只要它们的类型主题类型相同即可
class MyClass {
@Subject var subject1: String = ""
@Subject var subject2: String = ""
@Subject var subject3: String = ""
@Subject var subject4: String = ""
func observeText() {
$subject1.merged(with: $subject2, $subject3, $subject4)
.observeChange { changes in
// this will run if any of merged observable is set
print(changes.new)
}.retain()
}
}
你可以将最多 4 个 observables 组合为一个,并在设置其中任何一个 observable 时进行观察
class MyClass {
@Subject var userName: String = ""
@Subject var fullName: String = ""
@Subject var password: String = ""
@Subject var user: User = User()
func observeText() {
$userName.combine(with: $fullName, $password)
.mapped {
User(
userName: $0.new.0 ?? "",
fullName: $0.new.1 ?? "",
password: $0.new.2 ?? ""
)
}.relayChanges(to: $user)
.retain()
}
}
它将生成一个包含所有组合值的 Observable,但它是可选的,因为当触发其中一个 observable 时,某些值可能不存在。 为了确保仅在所有组合值都可用时才触发它,你可以使用 compactCombine
代替
class MyClass {
@Subject var userName: String = ""
@Subject var fullName: String = ""
@Subject var password: String = ""
@Subject var user: User = User()
func observeText() {
$userName.compactCombine(with: $fullName, $password)
.mapped {
User(
userName: $0.new.0,
fullName: $0.new.1,
password: $0.new.2
)
}.relayChanges(to: $user)
.retain()
}
}
在所有 observable 发出值之前,它不会被触发。
你懂的。 只需克隆并进行 pull request 即可