{"id":24352606,"url":"https://github.com/flowduino/observable","last_synced_at":"2026-05-09T06:36:26.812Z","repository":{"id":45319344,"uuid":"513424434","full_name":"Flowduino/Observable","owner":"Flowduino","description":"The most flexible and easiest to implement Observer Pattern platform for the Swift language (includes fully-functional Observable Thread!)","archived":false,"fork":false,"pushed_at":"2022-09-03T20:05:32.000Z","size":57,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-05-01T11:42:11.789Z","etag":null,"topics":["apple","ios","macos","observable","observation","observer","observer-pattern","observerpattern","swift","swift5","swift5-7","swiftui"],"latest_commit_sha":null,"homepage":"https://flowduino.com","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/Flowduino.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["Flowduino"]}},"created_at":"2022-07-13T07:34:43.000Z","updated_at":"2024-02-03T15:48:28.000Z","dependencies_parsed_at":"2022-08-28T13:52:44.287Z","dependency_job_id":null,"html_url":"https://github.com/Flowduino/Observable","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FObservable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FObservable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FObservable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FObservable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Flowduino","download_url":"https://codeload.github.com/Flowduino/Observable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243165501,"owners_count":20246722,"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":["apple","ios","macos","observable","observation","observer","observer-pattern","observerpattern","swift","swift5","swift5-7","swiftui"],"created_at":"2025-01-18T15:55:12.278Z","updated_at":"2026-05-09T06:36:26.771Z","avatar_url":"https://github.com/Flowduino.png","language":"Swift","funding_links":["https://github.com/sponsors/Flowduino"],"categories":[],"sub_categories":[],"readme":"# Observable\n\n\u003cp\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Swift-5.1%2B-yellowgreen.svg?style=flat\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/iOS-13.0+-865EFC.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/iPadOS-13.0+-F65EFC.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/macOS-10.15+-179AC8.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/tvOS-13.0+-41465B.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/watchOS-6.0+-1FD67A.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/License-MIT-blue.svg\" /\u003e\n    \u003ca href=\"https://github.com/apple/swift-package-manager\"\u003e\n      \u003cimg src=\"https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://discord.com/invite/GdZZKFTQ2A\"\u003e\n      \u003cimg src=\"https://img.shields.io/discord/878568176856731688?logo=Discord\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\nCollection of carefully-prepared Classes and Protocols designed to imbue your inheriting Object Types with efficient, protocol-driven Observer Pattern Behaviour.\nAs of version 2.0.0, this includes support for *Keyed Observers* (see usage examples below for details)\n\n## Installation\n### Xcode Projects\nSelect `File` -\u003e `Swift Packages` -\u003e `Add Package Dependency` and enter `https://github.com/Flowduino/Observable.git`\n\n### Swift Package Manager Projects\nYou can use `Observable` as a Package Dependency in your own Packages' `Package.swift` file:\n```swift\nlet package = Package(\n    //...\n    dependencies: [\n        .package(\n            url: \"https://github.com/Flowduino/Observable.git\",\n            .upToNextMajor(from: \"2.0.0\")\n        ),\n    ],\n    //...\n)\n```\n\nFrom there, refer to `Observable` as a \"target dependency\" in any of _your_ package's targets that need it.\n\n```swift\ntargets: [\n    .target(\n        name: \"YourLibrary\",\n        dependencies: [\n          \"Observable\",\n        ],\n        //...\n    ),\n    //...\n]\n```\nYou can then do `import Observable` in any code that requires it.\n\n## Usage\n\nHere are some quick and easy usage examples for the features provided by `Observable`:\n\n### ObservableClass\nYou can inherit from `ObservableClass` in your *own* Class Types to provide out-of-the-box Observer Pattern support.\nThis not only works for `@ObservedObject` decorated Variables in a SwiftUI `View`, but also between your Classes (e.g. between Services, or Repositories etc.)\n\nFirst, you would define a Protocol describing the Methods implemented in your *Observer* Class Type that your *Observable* Class can invoke:\n```swift\n/// Protocol defining what Methods the Obverable Class can invoke on any Observer\nprotocol DummyObservable: AnyObject { // It's extremely important that this Protocol be constrained to AnyObject\n    func onFooChanged(oldValue: String, newValue: String)\n    func onBarChanged(oldValue: String, newValue: String)\n}\n```\n**Note** - It is important that our Protocol define the `AnyObject` conformity-constraint as shown above.\n\nNow, we can define our *Observable*, inheriting from `ObservableClass`:\n```swift\n/// Class that can be Observed \nclass Dummy: ObservableClass {\n    private var _foo: String = \"Hello\"\n    public var foo: String {\n        get {\n            return _foo\n        }\n        set {\n            // Invoke onFooChanged for all current Observers\n            withObservers { (observer: DummyObservable) in\n                observer.onFooChanged(oldValue: _foo, newValue: newValue)\n            }\n            _foo = newValue\n            objectWillChange.send() // This is for the standard ObservableObject behaviour (both are supported together)\n        }\n    }\n\n    private var _bar: String = \"World\"\n    public var bar: String {\n        get {\n            return _bar\n        }\n        set {\n            // Invoke onBarChanged for all current Observers\n            withObservers { (observer: DummyObservable) in\n                observer.onBarChanged(oldValue: _bar, newValue: newValue)\n            }\n            _bar = newValue\n            objectWillChange.send() // This is for the standard ObservableObject behaviour (both are supported together)\n        }\n    }\n}\n```\n\nWe can now define an *Observer* to register with the *Observable*, ensuring that we specify that it implements our `DummyObservable` protocol: \n```swift\nclass DummyObserver: DummyObservable {\n    /* Implementations for DummyObservable */\n    func onFooChanged(oldValue: String, newValue: String) {\n        print(\"Foo Changed from \\(oldValue) to \\(newValue)\")\n    }\n\n    func onBarChanged(oldValue: String, newValue: String) {\n        print(\"Bar Changed from \\(oldValue) to \\(newValue)\")\n    }\n}\n```\n\nWe can now produce some simple code (such as in a Playground) to put it all together:\n```swift\n// Playground Code to use the above\nvar observable = Dummy() // This is the Object that we can Observe\nvar observer = DummyObserver() // This is an Object that will Observe the Observable\n\nobservable.addObserver(observer) // This is what registers the Observer with the Observable!\nobservable.foo = \"Test 1\"\nobservable.bar = \"Test 2\"\n```\n\n### ObservableThreadSafeClass\n`ObservableThreadSafeClass` works exactly the same way as `ObservableClass`. The internal implementation simply encapsulates the `Observer` collections behind a `DispatchSemaphore`, and provides a *Revolving Door* mechanism to ensure unobstructed access is available to `addObserver` and `removeObserver`, even when `withObservers` is in execution.\n\nIts usage is exactly as shown above in `ObservableClass`, only you would substitute the inheritence of `ObservableClass` to instead inherit from `ObservableThreadSafeClass`.\n\n### ObservableThread\n`ObservableThread` provides you with a Base Type for any Thread Types you would want to Observe.\n\n**Note** - `ObservableThread` does implement the `ObservableObject` protocol, and is *technically* compatible with the `@ObservedObject` property decorator in a SwiftUI `View`. However, to use it in this way, anywhere you would invoke `objectWillUpdate.send()` you must instead use `notifyChange()`. Internally, `ObservableThread` will execute `objectWillChange.send()` **but** enforce that it must execute on the `MainActor` (as required by Swift)\n\nLet's now begin taking a look at how we can use `ObservableThread` in your code.\nThe example is intentionally simplistic, and simply generates a random number every 60 seconds within an endless loop in the Thread.\n\nLet's begin by defining our Observation Protocol:\n```swift\nprotocol RandomNumberObserver: AnyObject {\n    func onRandomNumber(_ randomNumber: Int)\n}\n```\nAny Observer for our Thread will need to conform to the RandomNumberObserver protocol above.\n\nNow, let's define our RandomNumberObservableThread class:\n```swift\nclass RandomNumberObservableThread: ObservableThread {\n    init() {\n        self.start() // This will start the thread on creation. You aren't required to do it this way, I'm just choosing to!\n    }\n\n    public override func main() { // We must override this method\n        while self.isExecuting { // This creates a loop that will continue for as long as the Thread is running!\n            let randomNumber = Int.random(in: -9000..\u003c9001) // We'll generate a random number between -9000 and +9000\n            // Now let's notify all of our Observers!\n            withObservers { (observer: RandomNumberObserver) in\n                observer.onRandomNumber(randomNumber)\n            }\n            Self.sleep(forTimeInterval: 60.00) // This will cause our Thread to sleep for 60 seconds\n        }\n    }\n}\n```\n\nSo, we now have a Thread that can be Observed, and will notify all Observers every minute when it generates a random Integer.\n\nLet's now implement a Class intended to Observe this Thread:\n```swift\nclass RandomNumberObserverClass: RandomNumberObserver {\n    public func onRandomNumber(_ randomNumber: Int) {\n        print(\"Random Number is: \\(randomNumber)\")\n}\n```\nWe can now tie this all together in a simple Playground:\n```swift\nvar myThread = RandomNumberObservableThread()\nvar myObserver = RandomNumberObserverClass()\nmyThread.addObserver(myObserver)\n```\n\nThat's it! The Playground program will now simply print out the new Random Number notice message into the console output every 60 seconds.\n\nYou can adopt this approach for any Observation-Based Thread Behaviour you require, because `ObservableThread` will always invoke the Observer callback methods in the execution context their own threads! This means that, for example, you can safely instantiate an Observer class on the UI Thread, while the code execution being observed resides in its own threads (one or many, per your requirements).\n\n### Keyed Observation Pattern\nAs of version 1.1.0, you can now register and notify *Keyed Observers*.\n**Note**: Version 2.0.0 modified the Interface significantly to eliminate the need for Generic Typing of the Key. Key Types are now inferred for you.\n\nThis functionality is an extension of the standard Observer Pattern, and is implemented in the following classes from which you can extend:\n- `KeyedObservableClass` instead of `ObservableClass`\n- `KeyedObservableThread` instead of `ObservableThread`\n- `KeyedObservableThreadSafeClass` instead of `ObservableThreadSafeClass`\n\n*Remember, Keyed Observation is an **extension** of the basic Observation Pattern, so any Keyed Observable is also inherently able to register and notify non-Keyed Observers*\n\nYou would use Keyed Observation whenever your Observers care about a specific context of change. A good example would be for a Model Repository, where an Observer may only care about changes to a specific Model contained in the Repository. In this scenario, you would used Keyed Observation to ensure the Observer is only being notified about changes corresponding to the given Key.\n\nKey Types must always conform to the `Hashable` protocol, just as must any Key Type used for a `Dictionary` collection.\n\nLet's take a look at a basic usage example.\n\nWe shall provide a basic usage example to synchronize an Observer's internal Dictionary for specific keys only with the values from the Observable's internal Dictionary.\n\nFirst, we would begin with an Observation Protocol:\n```swift\nprotocol TestKeyedObservable: AnyObject {\n    func onValueChanged(key: String, oldValue: String, newValue: String)\n}\n```\nThe above Observation Protocol provides the method `onValueChanged` which takes the `key` (in this case a `String` value) and provides the corresponding `oldValue` and `newValue` values for that `key`.\nOur *Observer* will implement `TestKeyedObservable` to provide an implementation for this function.\n\nNow, let's define a simple *Keyed Observable* to house the master Dictionary we will be selectively-synchronizing with one or more *Observers*.\n```swift\nclass TestKeyedObservableClass: KeyedObservableClass\u003cString\u003e {\n    private var keyValues: [String:String] = [\"A\":\"Hello\", \"B\":\"Foo\", \"C\":\"Ping\"]\n    \n    func setValue(key: String, value: String) {\n        withKeyedObservers(key: key) { (key, observer: TestKeyedObservable) in\n            observer.onValueChanged(key: key, oldValue: self.keyValues[key]!, newValue: value)\n        }\n        self.keyValues[key] = value\n    }\n}\n```\nThe above class inherits from `KeyedObservableClass` and specializes the `TKey` generic to be a `String`. In other words, the Keys for this Observable must always be `String` values.\nIt includes a simple `String:String` dictionary (`String` key with a `String` value)\n\nThe `setValue` method will simply notify all observers using `withKeyedObservers` any time a specific `key` the Obsever(s) is(are) observing is updated, passing along the `oldValue` and `newValue` values. It will then update its internal Dictionary (`keyValues`) so that it always contains the latest value.\n\nNote the use of `withKeyedObservers` instead of `withObservers`. You will use this syntax in your own Keyed Observables, changing only the declared Observer Protocol (`TestKeyedObservable` in this example) with the Observer Protocol representing your own observation methods.\n\nNow that we have a Keyed Observable that will notify Observers each time the value of a key changes, let's define an Observer.\n```swift\nclass TestKeyedObserverClass: TestKeyedObservable {\n    public var keyValues: [String:String] = [\"A\":\"Hello\", \"B\":\"Foo\"]\n    \n    func onValueChanged(key: String, oldValue: String, newValue: String) {\n        keyValues[key] = newValue\n    }\n}\n```\nSo, `TestKeyedObserverClass` is a simple class, implementing our `TestKeyedObservable` Observer Protocol.\nFor this example, we are going to presume that there are 2 pre-defined Keys with known initial values (there do not have to be... you can have as many keys as you wish)\n\nYou will notice that we initialized both the Observable and Observer classes to have identical `keyValues` dictionaries. This is solely for the sake of simplifying this example by ensuring there is always an `oldValue`. You don't need to do this in your own implementations.\n\nSo, now that we have the *Observable* and the *Observer* types, let's produce a simple bit of Playground code to tie it together.\n```swift\nlet observable = TestKeyedObservableClass() // Creates our Observable\nlet observer = TestKeyedObserverClass // Creates a single Observer instance\n```\nAt this point, we need to consider what Key or Keys our `observer` is going to Observe.\n\nFor example, we can Observe just one key:\n```swift\nobservable.addKeyedObserver(key: \"A\", observer)\n```\nThe above means that `observer` would only have its `onValueChanged` method invoked when the value of key *A* is modified in `observable`.\n\nLikewise, if we only care about key *B*, we can do:\n```swift\nobservable.addKeyedObserver(key: \"B\", observer)\n```\n\nIf we care about *both* known keys, we can simply register them both:\n```swift\nobservable.addKeyedObserver(keys: [\"A\", \"B\"], observer)\n```\n\nAlso, we can do something particularly clever and basically register the Observer for every Key known to its own Dictionary:\n```swift\nobservable.addKeyedObserver(keys: Array(observer.keyValues.keys), observer)\n```\nThe above would register `observer` with `observable` for every *key* contained in `observer`'s `keyValues` dictionary.\n\nUltimately, you can register the `observer` with the `observable` for any keys you want:\n```swift\nobservable.addKeyedObserver(key: \"Foo\", observer)\n```\n\nLet's output the initial values of all of our keys before we invoke any code that would modify their values:\n```swift\nfor (key, value) in observer.keyValues {\n    print(\"Key: '\\(key)' has a value of '\\(value)'\")\n}\n```\nThis would output:\n\u003e Key: 'A' has a value of 'Hello'\n\u003e Key: 'B' has a value of 'Foo'\n\nSo, now that we can register the Keyed Observer with the Observer for whatever key or keys we wish, let's trigger the Observer Pattern in the `observer`:\n```swift\nobservable.setValue(key: \"A\", \"World\")\n```\nThe above will then update the value if *A* from \"Hello\" to \"World\".\n\nIf we repeat the following code:\n```swift\nfor (key, value) in observer.keyValues {\n    print(\"Key: '\\(key)' has a value of '\\(value)'\")\n}\n```\nThis would output:\n\u003e Key: 'A' has a value of 'World'\n\u003e Key: 'B' has a value of 'Foo'\n\nOkay, so what if we change the value for key \"C\"? What will happen?\n```swift\nobservable.setValue(key: \"C\", \"Pong\")\n```\nNow, if we repeat the following code:\n```swift\nfor (key, value) in observer.keyValues {\n    print(\"Key: '\\(key)' has a value of '\\(value)'\")\n}\n```\nThis would output:\n\u003e Key: 'A' has a value of 'World'\n\u003e Key: 'B' has a value of 'Foo'\n\nNote that the `observer` was not notified about the change to the value of key *C*. This is because `observer` is not observing `observable` for changes to key *C*.\n\nThis is the value of Keyed Observation Pattern. Put simply: not all Observations are meaningful to all Observers. So, as you have now seen, Keyed Observeration enables our *Observers* to be notified specifically of changes relevant to that *Observer*.\n\n## Overloaded `addObserver`, `removeObserver`, `addKeyedObserver`, and `removeKeyedObserver` methods\nAs of version 1.1.0, all useful combination overloads for the above-specified methods of `ObservableClass`, `ObservableThread`, `ObservableThreadSafeClass`, `KeyedObservableClass`, `KeyedObservableThread`, and `KeyedObservableThreadSafeClass` have been provided to streamline the adding and removal of *Observers* with/from an *Observable*.\n\n### Adding a single *Observer* to an *Observable*\n```swift\nobservable.addObserver(myObserver)\n```\n\n### Adding multiple *Observers* to an *Observable*\n```swift\nobservable.addObserver([myObserver1, myObserver2, myObserver3])\n```\n\n### Adding a single *Keyed Observer* to a *Keyed Observable* with a single *Key*\n```swift\nkeyedObservable.addKeyedObserver(\"MyKey\", myKeyedObserver)\n```\n\n### Adding a single *Keyed Observer* to a *Keyed Observable* with multiple *Keys*\n```swift\nkeyedObservable.addKeyedObserver([\"Key1\", \"Key2\", \"Key3\"], myKeyedObserver)\n```\n\n### Adding multiple *Keyed Observers* to a *Keyed Observable* with a single *Key*\n```swift\nkeyedObservable.addKeyedObserver(\"MyKey\", [myKeyedObserver1, myKeyedObserver2, myKeyedObserver3])\n```\n\n### Adding multiple *Keyed Observers* to a *Keyed Observable* with multiple *Keys*\n```swift\nkeyedObservable.addKeyedObserver([\"Key1\", \"Key2\", \"Key3\"], [myKeyedObserver1, myKeyedObserver2, myKeyedObserver3])\n```\n\n`removeObserver` and `removeKeyedObserver` also provide the same overloads as shown above.\n\n## Additional Useful Hints\nThere are a few additional useful things you should know about this Package.\n### A single *Observable* can invoke `withObservers` for any number of *Observer Protocols*\nThis library intentionally performs run-time type checks against each registered *Observer* to ensure that it conforms to the explicitly-defined *Observer Protocol* being requested by your `withObservers` Closure method.\n\nSimple example protocols:\n```swift\nprotocol ObserverProtocolA: AnyObject {\n    func doSomethingForProtocolA()\n}\n\nprotocol ObserverProtocolB: AnyObject {\n    func doSomethingForProtocolB()\n}\n```\n\nWhich can then both be used by the same `ObservableClass`, `ObservableThreadSafeClass`, or `ObservableThread` descendant:\n\n```swift\nwithObservers { (observer: ObserverProtocolA) in\n    observer.doSomethingForProtocolA()\n}\n\nwithObservers { (observer: ObserverProtocolB) in\n    observer.doSomethingForProtocolB()\n}\n```\n\nAny number of *Observer Protocols* can be marshalled by any of our *Observable* types, and only *Observers* conforming to the explicitly-specified *Observer Protocol* will be passed into your `withObservers` Closure method.\n\n## License\n\n`Observable` is available under the MIT license. See the [LICENSE file](./LICENSE) for more info.\n\n## Join us on Discord\n\nIf you require additional support, or would like to discuss `Observable`, Swift, or any other topics related to Flowduino, you can [join us on Discord](https://discord.com/invite/GdZZKFTQ2A).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowduino%2Fobservable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflowduino%2Fobservable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowduino%2Fobservable/lists"}