SwiftObserver 6.3.0

SwiftObserver 6.3.0

Sebastian Telle 维护。



  • 作者
  • Flowtoolz

SwiftObserver

SwiftObserver

badge-pod badge-pms badge-languages badge-gitter badge-platforms badge-mit

SwiftObserver 是一个轻量级、响应式 Swift 框架。其设计目标使其易于学习和使用,非常愉快

  1. 有意义的代码 💡
    SwiftObserver 推广有意义的隐喻、名称和语法,产生易于阅读的代码。
  2. 非侵入式设计 ✊🏻
    SwiftObserver 不会限制或调节您的应用程序设计。它只是让做正确的事变得容易。
  3. 简洁 🕹
    SwiftObserver 采用几个极其简单的概念,并始终一致地应用它们,不例外。
  4. 灵活性 🤸🏻‍♀️
    SwiftObserver 的类型既简单又通用,可用于多种情况。
  5. 安全性
    SwiftObserver 为您管理内存。是的,内存泄漏是不可能发生的。

SwiftObserver 只包含 1700 多行生产代码,但经过了 1000 多小时的工作,多次重新构思和改进,放弃了花哨的特性,编写了文档,进行了单元测试,并在实践中进行了实战测试。

为什么又是一个响应式 Swift 框架?

响应式编程 解决了有效架构实施的核心挑战:控制依赖关系方向,尤其是在使 特定关注点依赖于抽象关注点 方面。SwiftObserver 将响应式编程简化为其核心,即 观察者模式

SwiftObserver 与常规方法不同,它没有继承常见响应式库的隐喻、术语、类型、函数和运算符队伍。它不像 Rx 和 Combine 那样花哨,也不像 Redux 那样限制。相反,它提供了强大的简洁性,您可能会真正地 喜欢 使用。

目录

参与进来

开始使用

安装

使用 Swift 包管理器,您只需通过 Xcode(11+)添加 SwiftObserver 包(via Xcode)。

或者您手动调整项目的 Package.swift 文件。

// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .package(url: "https://github.com/flowtoolz/SwiftObserver.git",
                 .upToNextMajor(from: "6.2.0"))
    ],
    targets: [
        .target(name: "MyAppTarget",
                dependencies: ["SwiftObserver"])
    ]
)

然后运行 $ swift build$ swift run

使用 Cocoapods,调整你的 Podfile

target "MyAppTarget" do
  pod "SwiftObserver", "~> 6.2"
end

然后运行 $ pod install

最后,在你的 Swift 文件中

import SwiftObserver

介绍

无需学习一大堆任意的隐喻、术语或类型。

SwiftObserver非常简单:对象观察其他对象

或者更技术一点:可观察的向他们的观察者发送消息

就是这样。只有可读的代码

dog.observe(Sky.shared) { color in
    // marvel at the sky changing its color
}

观察者

任何对象都可以是一个观察者,如果它有一个用于接收消息的接收器

class Dog: Observer {
    let receiver = Receiver()
}

接收器维持观察者观察的生命。观察者只是牢牢地抓住它。

关于观察者的说明

  • 为了调用消息接收闭包,观察者/接收器必须仍然存活。在内存中没有死亡后的意识。
  • 观察者可以对同一可观察的进行多个同时观察,例如通过多次调用observe(...)
  • 您可以通过observer.isObserving(observable)来检查观察者是否正在观察可观察的。

可观察的

任何对象都可以是可观察的,如果它有一个用于发送消息的Messenger<Message>

class Sky: Observable {
    let messenger = Messenger<Color>()  // Message == Color
}

关于可观察者的说明

  • 可观察的通过send(_ message: Message)发送消息。可观察的客户端,甚至是它的观察者,都可以自由调用该函数。
  • Observable 对象以 send 被调用时的确切顺序传递消息,这有助于当观察者从其消息处理闭包中触发对 send 的进一步调用时。
  • 刚开始观察一个 Observable 不会 触发它发送消息。这保持了事物的简洁、可预测和一致性。

创建 Observable 的方式

  1. 创建一个 Messenger<Message>。它是一个中介,通过它其他实体进行通信。
  2. 创建一个自定义 Observable 类的实例,该类利用 Messenger<Message>
  3. 创建一个 Variable<Value>(也称为 Var<Value>)。它保存一个值并发送值更新。
  4. 创建一个 Promise<Value>。它有助于管理和组合异步调用的过程。
  5. 创建一个 transform 对象。它包装并转换另一个 Observable

内存管理

ObserverObservable 死亡时,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

自由观察者

你可以在不显式设置观察者的情况下开始观察

observe(Sky.shared) { color in
    // marvel at the sky changing its color
}

Sky.shared.observed { color in  // ... same
    // ...
}

这两个示例都内部使用全局观察者 FreeObserver.shared。你可以显式引用 FreeObserver.shared 来停止特定或所有此类免费全局观察。

你还可以实例化自己的 FreeObserver 来进行更加“自由”的观察。只需保持其活动状态,直到观察持续的时间即可。这样的自由观察者像其他响应式框架中的“Cancellable”或“Token”。

你还可以进行 一次性 观察

observeOnce(Sky.shared) { color in
    // notice new color. observation has stopped.
}

Sky.shared.observedOnce { color in  // ... same
    // ...
}

这两个函数都返回涉及的 FreeObserver 作为可丢弃的结果。通常你会忽略该观察者,它将在收到一条消息后与观察一起死亡。

通讯者

通讯者是最简单的Observable,其他所有Observable的基础。它本身不发送消息,但任何人都可以通过它发送消息,并用于任何类型的信息。

let textMessenger = Messenger<String>()

observer.observe(textMessenger) { textMessage in
    // respond to textMessage
}

textMessenger.send("my message")

通讯者实现了常见的通讯者/通知模式,可以直接用于此目的。

理解Observable

拥有一个通讯者实际上定义了Observable对象

public protocol Observable: class {
    var messenger: Messenger<Message> { get }
    associatedtype Message: Any
}

通讯者本身也是Observable,因为它将自身作为所需的通讯者

extension Messenger: Observable {
    public var messenger: Messenger<Message> { self }
}

其他每个Observable类要么是通讯者的子类,要么是提供通讯者的定制Observable类。定制观察者通常使用某些enum作为它们的消息类型。

class Model: SuperModel, Observable {
    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>是一个具有属性value: ValueObservable

观察变量

只要其value发生变化,Var<Value>就会发送一个类型为Update<Value>的消息,通知旧值和新值。

let number = Var(42)

observer.observe(number) { update in
    let whatsTheBigDifference = update.new - update.old
}

此外,您可以始终手动调用variable.send()(不带参数)来发送一个包含旧值和新值都为当前值的更新消息(见缓存消息)。

使用变量值

Value 必须是 Equatable 的,且根据其 value 属性,整个 Var<Value> 也是 Equatable 的。当 Value 是可比较的,则 Var<Value> 也可比较。

你可以通过初始化器直接指定 value 或者通过 `<- 运算符。

let text = Var<String?>()    // text.value == nil
text.value = "a text"
let number = Var(23)         // number.value == 23
number <- 42                 // number.value == 42

数值

如果 Value 是某种数字类型 Number,它可以是 IntFloatDouble

  1. 每个 Var<Number>Var<Number?>Var<Number>?Var<Number?>? 都有相应的属性 var int: Intvar float: Floatvar double: Double。该属性是必需的,将 nil 值解释为零。

  2. 你可以对所有成对的 NumberNumber?Var<Number>Var<Number?>Var<Number>?Var<Number?>? 应用数字运算符 +-*/

let numVar = Var<Int?>()     // numVar.value == nil
print(numVar.int)            // 0
numVar.int += 5              // numVar.value == 5
numVar <- Var(1) + 2         // numVar.value == 3

字符串

  1. 每个 Var<String>Var<String?>Var<String>?Var<String?>? 都有一个属性 var string: String。该属性是必需的,将 nil 值解释为空字符串 ""
  2. 你可以对所有成对的 StringString?Var<String>Var<String?>Var<String>?Var<String?>? 应用连接操作符 +
  3. 代表其 string 属性,每个 Var<String>Var<String?> 都遵守 TextOutputStreamBidirectionalCollectionCollectionSequenceCustomDebugStringConvertibleCustomStringConvertible

编码和解码变量

每个 Var<Value> 都是 Codable,且其 Value 也必须是 Codable。所以当你的一些类型有 Var 属性时,你仍然可以通过简单地遵循 Codable 协议来使该类型 Codable

class Model: Codable {
    private(set) var text = Var("String Variable")
}

请注意,text 是一个 var 而不是 let。因为它不能是常量,因为 Swift 的隐式解码器必须修改它。但是,Model 的客户端应该只设置 text.value 而不是 text 本身,因此设置器是私有的。

Promise

Promise<Value> 帮助管理异步返回,并使得这种意图更加明确。

注意事项: Promise 是 SwiftObserver 的一部分,因为 Combine 的 Future 不幸地不是一个实际的单次异步调用的解决方案,在合理简单的环境中依赖 PromiseKit 可能是不必要的,而且 Vapor/NIO 的 Async 也可能过于特定于服务器。无论如何,将承诺作为常规可观察对象集成可以带来一些一致性、简单性和协同效应。然而,由于 Swift 的 async/await,在某个时候,所有承诺/未来的实现都将变得过时。

接收承诺值

只接收一次

func getID() -> Promise<Int> {   // getID() promises an Int
    Promise { promise in         // convenience initializer
        getIDAsync { id in       // handler retains the promise until it's fulfilled
            promise.fulfill(id)  // triggers message: Promise.Event.wasFulfilled(id)
        } 
    }
}

getID().whenFulfilled { id in    // get id (if fulfilled) or observe promise
    // do somethin with the ID
}

因为一个 Promise 可能已经被实现,所以我们通常 直接观察它。相反,我们调用 whenFulfilled

通常,承诺是短暂的观察对象,我们不会保留它。由于异步函数(如 getID())返回承诺以实现它而使承诺保持活动状态,所以即使没有在任何地方持有承诺,我们也可以异步地获取承诺值。当承诺实现并终止时,承诺及其观察会被自动清理。

再次接收它

有时,在收到异步结果后(长时间)进行多项操作

let idPromise = getID()           // Promise<Int>

idPromise.whenFulfilled { id in
    // do somethin with id
}

idPromise.whenFulfilled { id in
    // do somethin else with id
}

组合Promise

受到PromiseKit的启发,SwiftObserver支持使用Promise进行异步调用的组合。

顺序组合

promise {                   // establish context and increase readability 
    getInt()                // return a Promise<Int>
}.then {                    // chain another promise sequentially
    getString(takeInt: $0)  // take Int sent by 'promise', return a Promise<String>
}.whenFulfilled {           // observation dies when promise 'then' is fulfilled
    print($0)               // print String sent by promise 'then'
}

promise用于提高可读性。它允许使用一致的闭包语法,并清楚地表明我们正在处理Promise。它接受一个返回Promise的闭包,并简单地返回那个Promise

您在第一个Promise上调用then,并传入一个返回第二个Promise的闭包。该闭包接受第一个Promise的值,允许第二个Promise依赖于它。then返回一个新的Promise,它提供了第二个Promise的值。

并发组合

promise {                    
    getInt()                
}.and {                     // chain another promise concurrently
    getString()             
}.whenFulfilled {                
    print($0.0)             // print Int sent by 'promise'
    print($0.1)             // print String sent by promise 'getString()'
}

Promise上调用and并传入一个返回另一个Promise的闭包。这立即观察两个Promise。and返回一个新的Promise,它提供了两个Promise的合并值。

值映射

promise {                    
    getInt()                
}.whenFulfilled {           // returns 'promise' so the chain can continue
    print($0)               // print Int sent by 'promise'
}.map {                     // chain a mapping promise sequentially
    "\($0)"                 // map Int sent by 'promise' to String
}.whenFulfilled {                
    print($0)               // print String sent by promise 'map'
}

变换函数既不过滤消息也不创建独立变换时,在Promise上调用时实际上返回一个新的Promise。这些函数是map(...)unwrap(default)new()。这里的优点是,与返回Promise的任何函数一样,您不需要保持可观察对象存活,以便观察它。

变换

转换使消息处理中的常见步骤更加简洁和易于阅读。它们允许以多种方式映射、过滤和解包消息。您可以将这些转换自由地链接在一起,还可以用它们定义新的转换。

以下示例将类型为 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
}

使转换可观测

您可以直接在线上转换特定的观察结果,如上述示例所示。这样的临时转换给观察者提供了很大的灵活性。

或者您可以实例化一个新的具有转换链的 Observable 对象。上面的例子可以这样显示

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
}

每个转换对象将其基础 Observable 作为 origin 暴露出来。您甚至可以替换 origin

let titleLength = Var("Dummy Title").new().map { $0.count }
let title = Var("Real Title")
titleLength.origin.origin = title

这种独立的转换可以为多个观察者提供相同的预处理。但由于这些转换是不同的 Observable 对象,您必须在某个地方强有力地持有它们。将转换链作为专用可观测对象持有适合像表示其他数据转换的视图模型之类的实体。

使用预构建的转换

无论您是临时应用转换还是作为独立对象,它们的工作方式相同。以下列表说明了作为可观测对象的预构建转换。

映射

首先,您的常规熟悉 map 函数。它转换消息并通常也转换它们的类型

let messenger = Messenger<String>()          // sends String
let stringToInt = messenger.map { Int($0) }  // sends Int?

当一个像 Var<Value> 这样的 Observable 发送类型为 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 仅接收特定消息。<code>select 与所有<code>Equatable消息类型一起工作。<code>select将消息类型映射到<code>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"
}

展开

有时,我们将消息类型设为可选的,例如当<code>Var没有有意义的基础值时。但通常我们不想处理可选值。因此我们使用<code>unwrap(),完全忽略<code>nil消息。

let errorCodes = Messenger<Int?>()     // sends Int?       
let errorAlert = errorCodes.unwrap()   // sends Int if the message is not nil

默认展开

您还可以通过替换<code>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
}

当然,如上所示的临时转换在处理实际消息时结束。现在,当链中的最后一个转换也为其处理接收一个闭包参数,像<code>map和<code>filter一样时,我们使用<code>receive来保持<a href="https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID102" rel="nofollow">尾随闭包的优雅语法。

dog.observe(Sky.shared).map {
    $0 == .blue     
}.receive {
    print("Will we go outside? \($0 ? "Yes" : "No")!")
} 

高级

信息作者

每条消息都关联一个作者。这个功能如果使用,在代码中才会显现出来。

可以使用 observable.send(message, from: author) 方法让可观察对象发送一个作者与消息。如果没有指定作者,如 observable.send(message),则可观察对象本身成为作者。

变异变量

变量有一个特殊的值设置器,这允许识别变更作者

let number = Var(0)
number.set(42, as: controller) // controller becomes author of the update message

接收作者

观察者可以通过将作者作为消息处理闭包的参数添加来接收作者。

observer.observe(observable) { message, author in
    // process message from author
}

通过作者,观察者可以确定信息的来源。在简单的消息传递模式中,来源将是消息发送者。

共享可观察对象

当多个观察者观察相同的可观察对象,而其操作可能发送通知时,识别信息作者可能变得至关重要。

可表示的数据是这种共享可观察对象的一种常见类型。例如,当多个实体观察并修改存储抽象或缓存层次结构时,他们常常希望避免对自己动作的反应。这种过度反应可能导致不必要的重复工作或无限响应循环。因此,当修改数据时,他们会将自己识别为变更作者,并在观察时忽略来自 self 的消息。

class Collaborator: Observer {
    func observeText() {
        observe(sharedText) { update, author in
            guard author !== self else { return }
            // someone else edited the text
        }
    }
  
    func editText() {
        sharedText.set("my new text", as: self) // identify as author when editing
    }
  
    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

缓存的消息

ObservableCache 是一个 Observable,它有一个属性 latestMessage: Message,通常返回最后发送的消息或指示没有变化的消息。

ObservableCache 还提供了两个便利函数

  • send() 不接受任何参数,并发送 latestMessage
  • whenCached 可用,其中 Message 是可选的。它在有新消息可用时异步提供消息。如果缓存中的 latestMessage 不是 nil,则 whenCached 立即提供该消息;否则,它将观察缓存,直到缓存发送一条不是 nil 的消息。

四种缓存

  1. 任何 Var 都是一个 ObservableCache。它的 latestMessage 是一个 Update,其中 oldnew 都包含当前的 value

  2. 在对一个 Observable 调用 cache() 会创建一个 变换,这个变换是一个 ObservableCache。那个缓存的消息将是可选的,但永远不会是一个 可选可选,即使原始的 Message 已经是可选的。

    当然,cache() 不能作为观察的临时变换,所以它只能创建一个独立的可观察对象。

  3. 任何原始是 ObservableCache 的变换如果是没有抑制(过滤)消息的,那么它本身就隐式地是一个 ObservableCache。这些兼容变换包括:mapnewunwrap(default)

    请注意,一个隐式 ObservableCache 变换的 latestMessage 返回其底层 ObservableCache 原始的转换后的 latestMessage。在该变换上调用 send(transformedMessage) 不会“更新”其 latestMessage

  4. 自定义可观察对象可以轻松地符合 ObservableCache。即使它们的消息类型不是基于某种状态,latestMessage 仍然可以返回一个有意义的默认值——甚至是当 Message 是可选的时候。

基于状态的消息

类似于 VarObservable,它从其状态推导消息,可以按需生成“最新消息”,因此可以作为 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
            }
        }
    }
}

弱可观察对象

当您想要将一个 Observable 放入某种数据结构中或作为 origin 放入一个 transform 中,并将它作为一个 weak 引用保存在那里时,您可以通过调用 observable.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() 不能作为临时的变换,所以它只能创建一个独立的可观察对象。

更多

进一步阅读

开放任务

  • 分解、重做并扩展单元测试套件
  • 编写 API 文档注释
  • 更新、重做并扩展功能、哲学和模式的文档
  • 添加与 Combine 和 SwiftUI 交互的绑定
  • 如果添加 API 复杂度值得,添加在队列上观察/处理时的语法糖
  • 在提供任何好处的地方利用属性包装器
  • 参与反馈和贡献