{"id":15156598,"url":"https://github.com/nohype-ai/SwiftObserver","last_synced_at":"2025-10-24T14:30:36.075Z","repository":{"id":60739809,"uuid":"130476021","full_name":"codeface-io/SwiftObserver","owner":"codeface-io","description":"Easy Elegant Reactive Swift #NoRx","archived":false,"fork":false,"pushed_at":"2024-02-24T10:09:27.000Z","size":24865,"stargazers_count":29,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-31T01:56:38.690Z","etag":null,"topics":["architecture","callback","clean-architecture","clean-code","combine","dependency-inversion","dependency-inversion-principle","norx","notifications","observer","observer-pattern","reactive","reactive-programming","redux","rxswift","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codeface-io.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-04-21T13:25:57.000Z","updated_at":"2024-12-05T11:51:45.000Z","dependencies_parsed_at":"2023-07-14T10:31:36.790Z","dependency_job_id":null,"html_url":"https://github.com/codeface-io/SwiftObserver","commit_stats":{"total_commits":753,"total_committers":4,"mean_commits":188.25,"dds":0.003984063745019917,"last_synced_commit":"d1b62a104711200c36a627ea43f202153f66d70a"},"previous_names":["flowtoolz/swiftobserver"],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FSwiftObserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FSwiftObserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FSwiftObserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeface-io%2FSwiftObserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codeface-io","download_url":"https://codeload.github.com/codeface-io/SwiftObserver/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237981878,"owners_count":19397145,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","callback","clean-architecture","clean-code","combine","dependency-inversion","dependency-inversion-principle","norx","notifications","observer","observer-pattern","reactive","reactive-programming","redux","rxswift","swift"],"created_at":"2024-09-26T19:23:43.740Z","updated_at":"2025-10-24T14:30:31.000Z","avatar_url":"https://github.com/codeface-io.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![SwiftObserver](Documentation/swift.png)\n\n# SwiftObserver\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fcodeface-io%2FSwiftObserver%2Fbadge%3Ftype%3Dswift-versions\u0026style=flat-square)](https://swiftpackageindex.com/codeface-io/SwiftObserver) \u0026nbsp;[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fcodeface-io%2FSwiftObserver%2Fbadge%3Ftype%3Dplatforms\u0026style=flat-square)](https://swiftpackageindex.com/codeface-io/SwiftObserver) \u0026nbsp;[![](https://img.shields.io/badge/Documentation-DocC-blue.svg?style=flat-square)](https://swiftpackageindex.com/codeface-io/SwiftObserver/documentation) \u0026nbsp;[![](https://img.shields.io/badge/License-MIT-lightgrey.svg?style=flat-square)](LICENSE)\n\nSwiftObserver is a lightweight package for reactive Swift. Its design goals make it easy to learn and a joy to use:\n\n1. **Meaningful Code** 💡\u003cbr\u003eSwiftObserver promotes meaningful metaphors, names and syntax, producing highly readable code.\n2. **Non-intrusive Design** ✊🏻\u003cbr\u003eSwiftObserver doesn't limit or modulate your design. It just makes it easy to do the right thing.\n3. **Simplicity** 🕹\u003cbr\u003eSwiftObserver employs few radically simple concepts and applies them consistently without exceptions.\n4. **Flexibility** 🤸🏻‍♀️\u003cbr\u003eSwiftObserver's types are simple but universal and composable, making them applicable in many situations.\n5. **Safety** ⛑\u003cbr\u003eSwiftObserver eliminates the memory leaks that such an easy to use observer-/reactive library might invite.\n\nSwiftObserver is only 1400 lines of production code, but it's well beyond 1000 hours of work. With precursor implementations going back to 2013, it has continuously been re-imagined, reworked and battle-tested, [letting go of many fancy features](https://github.com/codeface-io/SwiftObserver/releases) while refining documentation and [unit-tests](https://github.com/codeface-io/SwiftObserver/tree/master/Tests/SwiftObserverTests).\n\n## Why the Hell Another Reactive Swift Framework?\n\n[*Reactive Programming*](https://en.wikipedia.org/wiki/Reactive_programming) adresses the central challenge of implementing effective architectures: controlling dependency direction, in particular making [specific concerns depend on abstract ones](https://en.wikipedia.org/wiki/Dependency_inversion_principle). SwiftObserver breaks reactive programming down to its essence, which is the [*Observer Pattern*](https://en.wikipedia.org/wiki/Observer_pattern).\n\nSwiftObserver diverges from convention as it doesn't inherit the metaphors, terms, types, or function- and operator arsenals of common reactive libraries. It's not as fancy as Rx and Combine and not as restrictive as Redux. Instead, it offers a powerful simplicity you might actually **love** to work with.\n\n## Contents\n\n* [Introduction](#introduction)\n    * [Get Involved](#get-involved)\n    * [Install](#install)\n    * [Get Started](#get-started)\n* [Messengers](#messengers)\n    * [Understand Observable Objects](#understand-observable-objects)\n* [Variables](#variables)\n    * [Observe Variables](#observe-variables)\n    * [Access Variable Values](#access-variable-values)\n    * [Encode and Decode Variables](#encode-and-decode-variables)\n* [Transforms](#transforms)\n    * [Make Transforms Observable](#make-transforms-observable)\n    * [Use Prebuilt Transforms](#use-prebuilt-transforms)\n    * [Chain Transforms](#chain-transforms)\n* [Advanced](#advanced)\n    * [Interoperate With Combine](#interoperate-with-combine)\n    * [Pull Latest Messages](#pull-latest-messages)\n    * [Identify Message Authors](#identify-message-authors)\n    * [Observe Weak Objects](#observe-weak-objects)\n* [More](#more)\n\n# Introduction\n\n## Get Involved\n\n* Found a **bug**? Create a [github issue](https://github.com/codeface-io/SwiftObserver/issues/new/choose).\n* Need a **feature**? Create a [github issue](https://github.com/codeface-io/SwiftObserver/issues/new/choose).\n* Want to **improve** stuff? Create a [pull request](https://github.com/codeface-io/SwiftObserver/pulls).\n* Need **support** and troubleshooting? Write at \u003chello@codeface.io\u003e.\n* Want to **contact** us? Write at \u003chello@codeface.io\u003e.\n\n## Install\n\nWith the [**Swift Package Manager**](https://github.com/apple/swift-package-manager/tree/master/Documentation#swift-package-manager), you add the SwiftObserver package [via Xcode](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app) (11+).\n\nOr you manually adjust the [Package.swift](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#create-a-package) file of your project:\n\n~~~swift\n// swift-tools-version:5.6.0\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"MyProject\",\n    platforms: [\n        .iOS(.v12), .macOS(.v10_14), .tvOS(.v12), .watchOS(.v6)\n    ],\n    products: [\n        .library(\n            name: \"MyProject\",\n            targets: [\"MyProject\"]\n        )\n    ],\n    dependencies: [\n        .package(\n            url: \"https://github.com/codeface-io/SwiftObserver.git\",\n            exact: \"7.0.3\"\n        )\n    ],\n    targets: [\n        .target(name: \"MyProject\",\n                dependencies: [\"SwiftObserver\"])\n    ]\n)\n~~~\n\nThen run `$ swift build` or `$ swift run`.\n\nFinally, in your **Swift** files:\n\n```swift\nimport SwiftObserver\n```\n\n## Get Started\n\nNo need to learn a bunch of arbitrary metaphors, terms or types.\n\nSwiftObserver is simple: **Objects *observe* other objects**.\n\nOr a tad more technically: ***Observable objects* send *messages* to their *observers***.\n\nThat's it. Just readable code:\n\n~~~swift\ndog.observe(Sky.shared) { color in\n    // marvel at the sky changing its color\n}\n~~~\n\n### Observers\n\nAny object can be an `Observer` if it has a `Receiver` for receiving messages:\n\n```swift\nclass Dog: Observer {\n    let receiver = Receiver()\n}\n```\n\nThe receiver keeps the observer's observations alive. The observer just holds on to it strongly.\n\n#### Notes on Observers\n\n* For a message receiving closure to be called, the `Observer`/`Receiver` must still be alive. There's no awareness after death in memory.\n* An `Observer` can do multiple simultaneous observations of the same `ObservableObject`, for example by calling `observe(...)` multiple times.\n* You can check wether an `observer` is observing an `observable` via `observer.isObserving(observable)`.\n\n### Observable Objects\n\nAny object can be an `ObservableObject` if it has a `Messenger\u003cMessage\u003e` for sending messages:\n\n```swift\nclass Sky: ObservableObject {\n    let messenger = Messenger\u003cColor\u003e()  // Message == Color\n}\n```\n\n#### Notes on Observable Objects\n\n* An `ObservableObject` sends messages via `send(_ message: Message)`. The object's clients, even its observers, are also free to call that function.\n* An `ObservableObject` delivers messages in exactly the order in which `send` is called, which helps when observers, from their message handling closures, somehow trigger further calls of `send`.\n* Just starting to observe an `ObservableObject` does **not** trigger it to send a message. This keeps everything simple, predictable and consistent.\n\n#### Ways to Create an Observable Object\n\n1. Create a [`Messenger\u003cMessage\u003e`](#messengers). It's a mediator through which other entities communicate.\n2. Create an object of a [custom `ObservableObject`](#understand-observable-objects) class that utilizes `Messenger\u003cMessage\u003e`.\n3. Create a [`Variable\u003cValue\u003e`](#variables) (a.k.a. `Var\u003cValue\u003e`). It holds a value and sends value updates.\n5. Create a [*transform*](#make-transforms-observable) object. It wraps and transforms another `ObservableObject`.\n\n### Memory Management\n\nWith SwiftObserver, you don't have to deal with \"Cancellables\", \"Tokens\", \"DisposeBags\" or any such weirdness for every new observation. And yet, you also don't have to worry about any specific memory management. When an `Observer` or `ObservableObject` dies, SwiftObserver cleans up all related observations automatically.\n\nOf course, observing- and observed objects are still free to stop particular or all their ongoing observations:\n\n```swift\ndog.stopObserving(Sky.shared)          // no more messages from the sky\ndog.stopObserving()                    // no more messages from anywhere\nSky.shared.stopBeingObserved(by: dog)  // no more messages to dog\nSky.shared.stopBeingObserved()         // no more messages to anywhere\n```\n\n# Messengers\n\n`Messenger` is the simplest `ObservableObject` and the basis of every other `ObservableObject`. It doesn't send messages by itself, but anyone can send messages through it and use it for any type of message:\n\n```swift\nlet textMessenger = Messenger\u003cString\u003e()\n\nobserver.observe(textMessenger) { textMessage in\n    // respond to textMessage\n}\n\ntextMessenger.send(\"my message\")\n```\n\n`Messenger` embodies the common [messenger / notifier pattern](Documentation/specific-patterns.md#the-messenger-pattern) and can be used for that out of the box. \n\n## Understand Observable Objects\n\nHaving a `Messenger` is actually what defines an `ObservableObject`:\n\n```swift\npublic protocol ObservableObject: AnyObject {\n    var messenger: Messenger\u003cMessage\u003e { get }\n    associatedtype Message: Any\n}\n```\n\n`Messenger` is itself an `ObservableObject` because it points to itself as the required `Messenger`:\n\n```swift\nextension Messenger: ObservableObject {\n    public var messenger: Messenger\u003cMessage\u003e { self }\n}\n```\n\nEvery other `ObservableObject` class is either a subclass of `Messenger` or a custom `ObservableObject` class that provides a `Messenger`. Custom observable objects often employ some `enum` as their message type:\n\n```swift\nclass Model: SuperModel, ObservableObject {\n    func foo() { send(.willUpdate) }\n    func bar() { send(.didUpdate) }\n    deinit { send(.willDie) }\n    let messenger = Messenger\u003cEvent\u003e()  // Message == Event\n    enum Event { case willUpdate, didUpdate, willDie }\n}\n```\n\n# Variables\n\n `Var\u003cValue\u003e` is an `ObservableObject` that has a property `var value: Value`. \n\n## Observe Variables\n\nWhenever its `value` changes, `Var\u003cValue\u003e` sends a message of type `Update\u003cValue\u003e`, informing about the `old` and `new` value:\n\n~~~swift\nlet number = Var(42)\n\nobserver.observe(number) { update in\n    let whatsTheBigDifference = update.new - update.old\n}\n\nnumber \u003c- 123  // use convenience operator \u003c- to set number.value\n~~~\n\nIn addition, you can always manually call `variable.send()` (without argument) to send an update in which `old` and `new` both hold the current `value` (see [Pull Latest Messages](#pull-latest-messages)).\n\n## Access Variable Values\n\nThe property wrapper `ObservableVar` allows to access the actual `Value` directly. Let's apply it to the above example:\n\n~~~swift\n@ObservableVar var number = 42\n\nobserver.observe($number) { update in\n    let whatsTheBigDifference = update.new - update.old\n}\n\nnumber = 123\n~~~\n\nThe wrapper's projected value provides the underlying `Var\u003cValue\u003e`, which you access via the `$` sign like in the above example. This is analogous to how you access underlying publishers of `@Published` properties in Combine.\n\n## Encode and Decode Variables\n\nA `Var\u003cValue\u003e` is automatically `Codable` if its `Value` is. So when one of your types has `Var` properties, you can make that type `Codable` by simply adopting the `Codable` protocol:\n\n~~~swift\nclass Model: Codable {\n    private(set) var text = Var(\"String Variable\")\n}\n~~~\n\nNote that `text` is a `var` instead of a `let`. It cannot be constant because Swift's implicit decoder must mutate it. However, clients of `Model` would be supposed to set only `text.value` and not `text` itself, so the setter is private.\n\n# Transforms\n\nTransforms make common steps of message processing more succinct and readable. They allow to map, filter and unwrap messages in many ways. You may freely chain these transforms together and also define new ones with them.\n\nThis example transforms messages of type `Update\u003cString?\u003e` into ones of type `Int`:\n\n```swift\nlet title = Var\u003cString?\u003e()\n\nobserver.observe(title).new().unwrap(\"Untitled\").map({ $0.count }) { titleLength in\n    // do something with the new title length\n}\n```\n\n## Make Transforms Observable\n\nYou may transform a particular observation directly on the fly, like in the above example. Such ad hoc transforms give the observer lots of flexibility.\n\nOr you may instantiate a new `ObservableObject` that has the transform chain baked into it. The above example could then look like this:\n\n```swift\nlet title = Var\u003cString?\u003e()\nlet titleLength = title.new().unwrap(\"Untitled\").map { $0.count }\n\nobserver.observe(titleLength) { titleLength in\n    // do something with the new title length\n}\n```\n\nEvery transform object exposes its underlying `ObservableObject` as `origin`. You may even replace `origin`:\n\n```swift\nlet titleLength = Var(\"Dummy Title\").new().map { $0.count }\nlet title = Var(\"Real Title\")\ntitleLength.origin.origin = title\n```\n\nSuch stand-alone transforms can offer the same preprocessing to multiple observers. But since these transforms are distinct `ObservableObject`s, you must hold them strongly somewhere. Holding transform chains as dedicated observable objects suits entities like view models that represent transformations of other data.\n\n## Use Prebuilt Transforms\n\nWhether you apply transforms ad hoc or as stand-alone objects, they work the same way. The following list illustrates prebuilt transforms as observable objects.\n\n### Map\n\nFirst, there is your regular familiar `map` function. It transforms messages and often also their type:\n\n```swift\nlet messenger = Messenger\u003cString\u003e()          // sends String\nlet stringToInt = messenger.map { Int($0) }  // sends Int?\n```\n\n### New\n\nWhen an `ObservableObject` like a `Var\u003cValue\u003e` sends *messages* of type `Update\u003cValue\u003e`, we often only care about  the `new` value, so we map the update with `new()`:\n\n~~~swift\nlet errorCode = Var\u003cInt\u003e()          // sends Update\u003cInt\u003e\nlet newErrorCode = errorCode.new()  // sends Int\n~~~\n\n### Filter\n\nWhen you want to receive only certain messages, use `filter`:\n\n```swift\nlet messenger = Messenger\u003cString\u003e()                     // sends String\nlet shortMessages = messenger.filter { $0.count \u003c 10 }  // sends String if length \u003c 10\n```\n\n### Select\n\nUse `select` to receive only one specific message. `select` works with all `Equatable` message types. `select` maps the message type onto `Void`, so a receiving closure after a selection takes no message argument:\n\n```swift\nlet messenger = Messenger\u003cString\u003e()                   // sends String\nlet myNotifier = messenger.select(\"my notification\")  // sends Void (no messages)\n\nobserver.observe(myNotifier) {                        // no argument\n    // someone sent \"my notification\"\n}\n```\n\n### Unwrap\n\nSometimes, we make message types optional, for example when there is no meaningful initial value for a `Var`. But we often don't want to deal with optionals down the line. So we can use `unwrap()`, suppressing `nil` messages entirely:\n\n~~~swift\nlet errorCodes = Messenger\u003cInt?\u003e()     // sends Int?       \nlet errorAlert = errorCodes.unwrap()   // sends Int if the message is not nil\n~~~\n\n### Unwrap with Default\n\nYou may also unwrap optional messages by replacing `nil` values with a default:\n\n~~~swift\nlet points = Messenger\u003cInt?\u003e()         // sends Int?       \nlet pointsToShow = points.unwrap(0)    // sends Int with 0 for nil\n~~~\n\n## Chain Transforms\n\nYou may chain transforms together:\n\n```swift\nlet numbers = Messenger\u003cInt\u003e()\n\nobserver.observe(numbers).map {\n    \"\\($0)\"                      // Int -\u003e String\n}.filter {\n    $0.count \u003e 1                 // suppress single digit integers\n}.map {\n    Int.init($0)                 // String -\u003e Int?\n}.unwrap {                       // Int? -\u003e Int\n    print($0)                    // receive and process resulting Int\n}\n```\n\nOf course, ad hoc transforms like the above end on the actual message handling closure. Now, when the last transform in the chain also takes a closure argument for its processing, like `map` and `filter` do, we use `receive` to stick with the nice syntax of [trailing closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID102):\n\n~~~swift\ndog.observe(Sky.shared).map {\n    $0 == .blue     \n}.receive {\n    print(\"Will we go outside? \\($0 ? \"Yes\" : \"No\")!\")\n} \n~~~\n\n# Advanced\n\n## Interoperate With Combine\n\n**CombineObserver** is another library product of the SwiftObserver package. It depends on SwiftObserver and adds a simple way to transform any SwiftObserver-`ObservableObject` into a Combine-`Publisher`:\n\n```swift\nimport CombineObserver\n\n@ObservableVar var number = 7               // SwiftObserver\nlet numberPublisher = $number.publisher()   // Combine\n\nlet cancellable = numberPublisher.dropFirst().sink { numberUpdate in\n    print(\"\\(numberUpdate.new)\")\n}\n\nnumber = 42 // prints \"42\"\n```\n\n\u003e This interoperation goes in only one direction. Here's some reasoning behind that: SwiftObserver is for pure Swift-/model code without external dependencies – not even on Combine. When combined with Combine (oops), SwiftObserver would be employed in the model core of an application, while Combine would be used more with I/O periphery like SwiftUI and other system-specific APIs that already rely on Combine. That means, the \"Combine layer\" might want to observe (react to-) the \"SwiftObserver layer\" – but hardly the other way around.\n\n## Pull Latest Messages \n\nAn `ObservableCache` is an `ObservableObject` that has a property `latestMessage: Message` which typically returns the last sent message or one that indicates that nothing has changed. `ObservableCache` has a function `send()` that takes no argument and sends `latestMessage`.\n\n### Four Kinds of `ObservableCache`\n\n1. Any `Var` is an `ObservableCache`. Its `latestMessage` is an `Update` in which `old` and `new` both hold the current `value`.\n\n2. Custom observable objects can easily conform to `ObservableCache`. Even if their message type isn't based on some state, `latestMessage` can still return a meaningful default value - or even `nil` where `Message` is optional.\n\n3. Calling `cache()` on an `ObservableObject` creates a [transform](#make-transforms-observable) that is an `ObservableCache`. That cache's `Message` will be optional but never an *optional optional*, even when the origin's `Message` is already optional.\n\n   Of course, `cache()` wouldn't make sense as an adhoc transform of an observation, so it can only create a distinct observable object.\n\n4. Any transform whose origin is an `ObservableCache` is itself implicitly an `ObservableCache` **if** it never suppresses (filters) messages. These compatible transforms are: `map`, `new` and `unwrap(default)`.\n\n   Note that the `latestMessage` of a transform that is an implicit `ObservableCache` returns the transformed `latestMessage` of its underlying `ObservableCache` origin. Calling `send(transformedMessage)` on that transform itself will not \"update\" its `latestMessage`.\n\n### State-Based Messages \n\nAn `ObservableObject` like `Var`, that derives its messages from its state, can generate a \"latest message\" on demand and therefore act as an `ObservableCache`:\n\n```swift\nclass Model: Messenger\u003cString\u003e, ObservableCache {  // informs about the latest state\n    var latestMessage: String { state }            // ... either on demand\n  \n    var state = \"initial state\" {\n        didSet {\n            if state != oldValue {\n                send(state)                        // ... or when the state changes\n            }\n        }\n    }\n}\n```\n\n## Identify Message Authors\n\nEvery message has an author associated with it. This feature is only noticable in code if you use it.\n\nAn observable object can send an author together with a message via `object.send(message, from: author)`. If noone specifies an author as in `object.send(message)`, the observable object itself becomes the author.\n\n### Mutate Variables\n\nVariables have a special value setter that allows to identify change authors:\n\n```swift\nlet number = Var(0)\nnumber.set(42, as: controller) // controller becomes author of the update message\n```\n\n### Receive Authors\n\nThe observer can receive the author, by adding it as an argument to the message handling closure:\n\n```swift\nobserver.observe(observableObject) { message, author in\n    // process message from author\n}\n```\n\nThrough the author, observers can determine a message's origin. In the plain messenger pattern, the origin would simply be the message sender.\n\n### Share Observable Objects\n\nIdentifying message authors can become essential whenever multiple observers observe the same object while their actions can cause it so send messages.\n\nMutable data is a common type of such shared observable objects. For example, when multiple entities observe and modify a storage abstraction or caching hierarchy, they often want to avoid reacting to their own actions. Such overreaction might lead to redundant work or inifnite response cycles. So they identify as change authors when modifying the data and ignore messages from `self` when observing it:\n\n```swift\nclass Collaborator: Observer {\n    func observeText() {\n        observe(sharedText).notFrom(self) { update, author in  // see author filters below\n            // someone else edited the text\n        }\n    }\n  \n    func editText() {\n        sharedText.set(\"my new text\", as: self)                // identify as change author\n    }\n  \n    let receiver = Receiver()\n}\n\nlet sharedText = Var\u003cString\u003e()\n```\n\n### Filter by Author\n\nThere are three transforms related to message authors. As with other transforms, we can apply them directly in observations or create them as standalone observable objects.\n\n#### Filter Author\n\nWe filter authors just like messages:\n\n```swift\nlet messenger = Messenger\u003cString\u003e()             // sends String\n\nlet friendMessages = messenger.filterAuthor {   // sends String if message is from friend\n    friends.contains($0)\n} \n```\n\n#### From\n\nIf only one specific author is of interest, filter authors with `from`. It captures the selected author weakly:\n\n```swift\nlet messenger = Messenger\u003cString\u003e()             // sends String\nlet joesMessages = messenger.from(joe)          // sends String if message is from joe\n```\n\n#### Not From\n\nIf **all but one** specific author are of interest, use `notFrom`. It also captures the excluded author weakly:\n\n```swift\nlet messenger = Messenger\u003cString\u003e()             // sends String\nlet humanMessages = messenger.notFrom(hal9000)  // sends String, but not from an evil AI\n```\n\n## Observe Weak Objects\n\nWhen you want to put an `ObservableObject` into some data structure or as the *origin* into a *transform* object but hold it there as a `weak` reference, transform it via `observableObject.weak()`:\n\n~~~swift\nlet number = Var(12)\nlet weakNumber = number.weak()\n\nobserver.observe(weakNumber) { update in\n    // process update of type Update\u003cInt\u003e\n}\n\nvar weakNumbers = [Weak\u003cVar\u003cInt\u003e\u003e]()\nweakNumbers.append(weakNumber)\n~~~\n\nOf course, `weak()` wouldn't make sense as an adhoc transform, so it can only create a distinct observable object.\n\n# More\n\n## Architecture\n\nHere's the internal architecture (composition and [essential](https://en.wikipedia.org/wiki/Transitive_reduction) dependencies) of the \"SwiftObserver\" target:\n\n![](Documentation/Architecture/SwiftObserver.png)\n\nMore diagrams of top-level source folders [are over here](Documentation/Architecture/architecture.md). The images were generated with [Codeface](https://codeface.io). \n\n## Further Reading\n\n* **DocC Documentation:** Check out the complete [reference documentation in DocC format](https://swiftpackageindex.com/codeface-io/SwiftObserver/documentation)\n* **Patterns *(incomplete)*:** Read more about some [patterns that emerged from using SwiftObserver](Documentation/specific-patterns.md#specific-patterns).\n* **Philosophy *(outdated)*:** Read more about the [philosophy and features of SwiftObserver](Documentation/philosophy.md#the-philosophy-of-swiftobserver).\n* **License:** SwiftObserver is released under the [MIT license](LICENSE).\n\n## Open Tasks\n\n* Update and rework (or simply delete) texts about philosophy and patterns\n* Engage feedback and contribution","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnohype-ai%2FSwiftObserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnohype-ai%2FSwiftObserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnohype-ai%2FSwiftObserver/lists"}