SwiftObserver

SwiftObserver

     

SwiftObserver 是一个轻量级的响应式 Swift 包。它的设计目标是易于学习和使用。

  1. 有意义的代码 💡
    SwiftObserver 提倡有意义的隐喻、名称和语法,从而产生高度可读的代码。
  2. 非侵入式设计 ✊🏻
    SwiftObserver 不会限制或调整您的设计。它只是让您更容易做正确的事情。
  3. 简洁性 🕹
    SwiftObserver 采用少数极其简单的概念,并始终如一地应用它们,没有任何例外。
  4. 灵活性 🤸🏻‍♀️
    SwiftObserver 的类型简单但通用且可组合,使其适用于许多情况。
  5. 安全性
    SwiftObserver 消除了这种易于使用的观察者/响应式库可能导致的内存泄漏。

SwiftObserver 只有 1400 行生产代码,但这背后是超过 1000 小时的工作。 自 2013 年的先驱实现以来,它不断地被重新构想、重新设计和经过实战检验, 放弃了许多花哨的功能,同时改进了文档和 单元测试

为什么还要另一个响应式 Swift 框架?

响应式编程解决了实现有效架构的核心挑战:控制依赖方向,特别是使特定关注点依赖于抽象的关注点。 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
}

观察者 (Observers)

任何对象都可以成为一个 Observer,如果它有一个 Receiver 用于接收消息

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

接收器保持观察者的观察活跃。 观察者只是强烈地抓住它。

关于观察者的说明

可观察对象 (Observable Objects)

任何对象都可以成为一个 ObservableObject,如果它有一个 Messenger<Message> 用于发送消息

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

关于可观察对象的说明

创建可观察对象的方法

  1. 创建一个Messenger<Message>。它是一个中介,其他实体通过它进行通信。
  2. 创建一个使用 Messenger<Message>自定义 ObservableObject 类的对象。
  3. 创建一个Variable<Value>(也称为 Var<Value>)。它保存一个值并发送值更新。
  4. 创建一个转换对象。它包装和转换另一个 ObservableObject

内存管理

使用 SwiftObserver,您不必为每次新的观察处理 “Cancellables”、“Tokens”、“DisposeBags” 或任何此类奇怪的东西。 而且,您也不必担心任何特定的内存管理。 当一个 ObserverObservableObject 死亡时,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

信使 (Messengers)

Messenger 是最简单的 ObservableObject,也是每个其他 ObservableObject 的基础。 它本身不发送消息,但任何人都可以通过它发送消息,并将其用于任何类型的消息

let textMessenger = Messenger<String>()

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

textMessenger.send("my message")

Messenger 体现了常见的 信使/通知器模式,并且可以开箱即用。

理解可观察对象 (Observable Objects)

拥有一个 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 }
}

变量 (Variables)

Var<Value> 是一个具有属性 var value: ValueObservableObject

观察变量 (Observe Variables)

每当它的 value 改变时,Var<Value> 发送一个类型为 Update<Value> 的消息,通知 oldnew

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()(不带参数)来发送一个更新,其中 oldnew 都保存当前 value(请参阅拉取最新消息)。

访问变量值 (Access Variable Values)

属性包装器 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 属性的底层发布者。

编码和解码变量 (Encode and Decode Variables)

如果 Var<Value>ValueCodable,则 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 是私有的。

转换 (Transforms)

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

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

使转换可观察 (Make Transforms Observable)

您可以像在上面的示例中一样,直接动态地转换特定的观察结果。 这种特殊的转换赋予观察者很大的灵活性。

或者您可以实例化一个新的 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,您必须在某处强烈地持有它们。 将转换链作为专用的可观察对象持有,适合于诸如视图模型之类的实体,这些实体表示其他数据的转换。

使用预构建的转换 (Use Prebuilt Transforms)

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

映射 (Map)

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

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

新建 (New)

当像 Var<Value> 这样的 ObservableObject 发送 Update<Value> 类型的消息时,我们通常只关心 new 值,因此我们使用 new() 映射更新。

let errorCode = Var<Int>()          // sends Update<Int>
let newErrorCode = errorCode.new()  // sends Int

过滤 (Filter)

如果您只想接收某些特定的消息,请使用 filter

let messenger = Messenger<String>()                     // sends String
let shortMessages = messenger.filter { $0.count < 10 }  // sends String if length < 10

选择 (Select)

使用 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"
}

解包 (Unwrap)

有时,我们将消息类型设置为可选类型,例如当 Var 没有有意义的初始值时。但是我们通常不想在后续操作中处理可选类型。因此,我们可以使用 unwrap(),完全抑制 nil 消息。

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

解包并提供默认值 (Unwrap with Default)

您还可以通过将 nil 值替换为默认值来解包可选消息。

let points = Messenger<Int?>()         // sends Int?       
let pointsToShow = points.unwrap(0)    // sends Int with 0 for nil

链式转换 (Chain Transforms)

您可以将多个转换链接在一起。

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
}

当然,像上面这样的临时转换最终会落在实际的消息处理闭包上。现在,当链中的最后一个转换也需要一个闭包参数进行处理时,比如 mapfilter,我们使用 receive 来保持 尾随闭包 的良好语法。

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

高级 (Advanced)

与 Combine 互操作 (Interoperate With Combine)

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 层” – 但反之则不然。

拉取最新消息 (Pull Latest Messages)

ObservableCache 是一个 ObservableObject,它具有一个属性 latestMessage: Message,通常返回最后发送的消息或指示没有任何更改的消息。 ObservableCache 有一个函数 send(),它不接受任何参数并发送 latestMessage

四种 ObservableCache

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

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

  3. ObservableObject 上调用 cache() 会创建一个 转换,该转换是一个 ObservableCache。该缓存的 Message 将是可选的,但永远不会是可选的可选,即使源的 Message 已经是可选的。

    当然,cache() 作为观察的临时转换没有意义,因此它只能创建一个不同的可观察对象。

  4. 如果任何转换的来源是 ObservableCache并且它从不抑制(过滤)消息,则该转换本身隐式地是一个 ObservableCache。这些兼容的转换是:mapnewunwrap(default)

    请注意,作为隐式 ObservableCache 的转换的 latestMessage 返回其底层 ObservableCache 源的转换后的 latestMessage。在该转换本身上调用 send(transformedMessage) 不会“更新”其 latestMessage

基于状态的消息 (State-Based Messages)

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
            }
        }
    }
}

识别消息作者 (Identify Message Authors)

每个消息都有一个与之关联的作者。只有当您使用它时,此功能才会体现在代码中。

可观察对象可以通过 object.send(message, from: author) 将作者与消息一起发送。如果没人指定作者,如 object.send(message) 中那样,则可观察对象本身将成为作者。

修改变量 (Mutate Variables)

变量有一个特殊的 setter,允许识别更改作者。

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

接收作者 (Receive Authors)

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

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

通过作者,观察者可以确定消息的来源。在普通的信使模式中,来源将只是消息发送者。

共享可观察对象 (Share Observable Objects)

当多个观察者观察同一对象,而他们的行为可能导致该对象发送消息时,识别消息作者变得至关重要。

可变数据是这种共享可观察对象的常见类型。例如,当多个实体观察和修改存储抽象或缓存层次结构时,它们通常希望避免对自己的行为做出反应。这种过度反应可能导致冗余工作或无限响应循环。因此,它们在修改数据时将自己标识为更改作者,并在观察数据时忽略来自 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>()

按作者过滤 (Filter by Author)

有三种与消息作者相关的转换。与其他转换一样,我们可以直接在观察中应用它们,或者将它们创建为独立的可观察对象。

过滤作者 (Filter Author)

我们像过滤消息一样过滤作者。

let messenger = Messenger<String>()             // sends String

let friendMessages = messenger.filterAuthor {   // sends String if message is from friend
    friends.contains($0)
} 

来自 (From)

如果只对一个特定作者感兴趣,请使用 from 过滤作者。它以弱引用的方式捕获选定的作者。

let messenger = Messenger<String>()             // sends String
let joesMessages = messenger.from(joe)          // sends String if message is from joe

非来自 (Not From)

如果对**除了一个之外的所有**特定作者感兴趣,请使用 notFrom。它也以弱引用的方式捕获排除的作者。

let messenger = Messenger<String>()             // sends String
let humanMessages = messenger.notFrom(hal9000)  // sends String, but not from an evil AI

观察弱对象 (Observe Weak Objects)

当您想将 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() 作为临时转换没有意义,因此它只能创建一个不同的可观察对象。

更多 (More)

架构 (Architecture)

这是“SwiftObserver”目标的内部架构(组合和基本依赖关系)。

更多顶级源文件夹的图表在此处。这些图像是使用 Codeface 生成的。

进一步阅读 (Further Reading)

未完成的任务 (Open Tasks)