{"id":32151164,"url":"https://github.com/riftvalleysoftware/rvs_generalobserver","last_synced_at":"2026-02-18T22:01:30.087Z","repository":{"id":63920402,"uuid":"291314022","full_name":"RiftValleySoftware/RVS_GeneralObserver","owner":"RiftValleySoftware","description":"A General-Purpose Observer/Observable Infrastructure","archived":false,"fork":false,"pushed_at":"2026-02-10T22:29:13.000Z","size":4307,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-11T01:37:30.584Z","etag":null,"topics":[],"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/RiftValleySoftware.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-08-29T17:08:12.000Z","updated_at":"2026-02-10T22:29:10.000Z","dependencies_parsed_at":"2023-01-14T14:00:50.270Z","dependency_job_id":null,"html_url":"https://github.com/RiftValleySoftware/RVS_GeneralObserver","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/RiftValleySoftware/RVS_GeneralObserver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RiftValleySoftware%2FRVS_GeneralObserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RiftValleySoftware%2FRVS_GeneralObserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RiftValleySoftware%2FRVS_GeneralObserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RiftValleySoftware%2FRVS_GeneralObserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RiftValleySoftware","download_url":"https://codeload.github.com/RiftValleySoftware/RVS_GeneralObserver/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RiftValleySoftware%2FRVS_GeneralObserver/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29596329,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T20:59:56.587Z","status":"ssl_error","status_checked_at":"2026-02-18T20:58:41.434Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2025-10-21T10:37:21.900Z","updated_at":"2026-02-18T22:01:30.081Z","avatar_url":"https://github.com/RiftValleySoftware.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Icon](icon.png)\n\n# RVS_GeneralObserver\n\n## DESCRIPTION\n\nThis is a general-purpose set of protocols that are designed to provide a simple infrastructure for a basic Observer implementation.\n\n[Here is the GitHub repo for the project.](https://github.com/RiftValleySoftware/RVS_GeneralObserver)\n\n[Here is the main documentation for the project.](https://riftvalleysoftware.github.io/RVS_GeneralObserver/)\n\n### Does Not Handle Messaging\n\nThis does not deal with messaging or managing communications between observers and observables. It simply gives them the infrastructure to track each other.\n\nThere are a few callbacks, explicitly related to aggregate management, but, otherwise, it's simply a tool to provide a reliable relationship graph management.\n\nIt is up to implementations to handle what to do with this information.\n\n### Protocol-Based\n\nThis is based on protocols, as opposed to classes or structs. A couple of the protocols require that they be implemented as classes. There is heavy reliance on protocol default implementations to deliver the infrastructure.\n\n## WHAT PROBLEM DOES THIS SOLVE?\n\nAt its heart, any observer implementation is really just a relationship graph. Observers _subscribe_ to Observables. Observables use the subscription as a one-way broadcast medium.\n\nManaging the subscriptions and relationships is absolutely fundamental to the pattern. If we can't trust our subscription list, then everything built upon it is at risk.\n\n### Different From Delegate\n\nApple uses the [Delegate](https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html) pattern in a lot of their Cocoa infrastucture. This is an excellent and simple pattern that has the following features:\n\n- Delegates are a \"one-to-one\" relationship. A class that has a delegate has only one delegate.\n- Delegates are often \"two-way\" relationships. Delegates can send information back to the objects to which they are subscribed. In fact, [the Data Source pattern](https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html#//apple_ref/doc/uid/TP40010810-CH11-SW6) codifies this explicitly.\n- Delegates require that all involved entities be classes. In fact, delegates need to derive from [NSObject](https://developer.apple.com/documentation/objectivec/nsobject).\n\nObservers (at least, they way I do them) have a different aspect:\n\n- They are usually a \"one-to-many\" relationship. Observers _subscribe_ to observables, who are responsible for managing a list of subscribers.\n- They are a \"one-way\" relationship. Observables can only send messages to subscribers. They cannot receive anything. If an observable wants to get messages from a subscriber, then the subscriber needs to become an observable, and the old observable needs to subscribe to them.\n- It isn't a requirement for observers and observables to be classes, to satisfy the pattern, but I do require that a couple of the protocols be class-based protocols, in order to implement some of the defaults.\n\n### Benefits\n\nManaging the subscription lists and relationships is a very fundamental part of Observer, and something that needs to be rock-solid. That was why this tool was developed.\n\nWe shouldn't even be thinking about this.\n\n## IMPLEMENTATION\n\n### [Swift Package Manager (SPM)](https://swift.org/package-manager/):\n\nThe URI for the repo is:\n\n- [git@github.com:RiftValleySoftware/RVS_GeneralObserver.git](git@github.com:RiftValleySoftware/RVS_GeneralObserver.git) (SSH), or\n- [https://github.com/RiftValleySoftware/RVS_GeneralObserver.git](https://github.com/RiftValleySoftware/RVS_GeneralObserver.git) (HTTPS).\n\nOnce you have the package included in your project (if you want to find out more about SPM, then you might want to [view this series](https://littlegreenviper.com/series/spm/)), you'll need to include the library. It will be a static (build-time) library:\n\n    import RVS_GeneralObserver\n\n### [Carthage](https://github.com/Carthage/Carthage):\n\nYou can include the library by adding the following line to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile):\n\n    github \"RiftValleySoftware/RVS_GeneralObserver\"\n    \nYou should probably just include the `Carthage/Checkouts/RVS_GeneralObserver/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift` file directly, as opposed to building a library.\n\n### [Git Submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) or Direct File Download\n\nIf you want to include the project as a submodule, simply use one of the URIs above (in the Swift Package Manager section). It's probably best to include [the `Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift` file](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/master/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift) directly from the submodule (with no module import).\n\nIf you want to simply download and include the file, then there is only one file to deal with. [The `Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift` file.](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/master/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift)\n\nJust download and include that one file. No need to import a module.\n\n### Observables\n\nOnce that is done, you can make a class (it needs to be a class) Observable, by conforming to the [`RVS_GeneralObservableProtocol`](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/5978359d3521f125b565e63767328ceec911a170/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift#L96) protocol.\n\nYou will need to create two stored properties in your implementation (the following examples are from [the unit tests](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/81f9021fe228f434df4fb17139620a57c7851412/Tests/RVS_GeneralObserverTest/RVS_GeneralObserverTests.swift#L39)):\n\n    class BaseObservable: RVS_GeneralObservableProtocol {\n        /* ############################################################## */\n        /**\n        The required UUID. It is set up with an initializer, and left alone.\n        */\n        let uuid = UUID()\n        \n        /* ############################################################## */\n        /**\n        This is the required observers Array.\n        */\n        var observers: [RVS_GeneralObserverProtocol] = []\n\nThe `uuid` property is a \"set and forget\" property. Simply do exactly as above, and never worry about it afterwards.\n\nThe `observers` Array is also one you're unlikely to use directly (but you'll probably cast it). It is where subscribers are tracked. This is how your observable will find broadcast targets. Normally, you'll probably cast this to an Array of more specific classes, like so:\n\n    var castArray: [MySpecificSubscriberThatConformsToRVS_GeneralObserverProtocol] { observers as? [MySpecificSubscriberThatConformsToRVS_GeneralObserverProtocol] ?? [] }\n\n### Observers\n\nWe have two types of Observers. One is a \"generic\" one, that can be applied to `struct`s and `class`es, that does not track the Observables to which an Observer is subscribed, and the other is a `class`-only variant that tracks an Observer's subscriptions:\n\nThese examples are also from the [unit tests](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/master/Tests/RVS_GeneralObserverTest/RVS_GeneralObserverTests.swift):\n\n[Standard (`struct` or `class`) Observer](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/81f9021fe228f434df4fb17139620a57c7851412/Tests/RVS_GeneralObserverTest/RVS_GeneralObserverTests.swift#L96):\n\n    struct BaseObserver: RVS_GeneralObserverProtocol {\n        /* ############################################################## */\n        /**\n         The required UUID. It is set up with an initializer, and left alone.\n         */\n        let uuid = UUID()\n\n[`class`-only Subscription-Tracking Observer](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/81f9021fe228f434df4fb17139620a57c7851412/Tests/RVS_GeneralObserverTest/RVS_GeneralObserverTests.swift#L196):\n\n    class SubTrackerObserver: RVS_GeneralObserverSubTrackerProtocol {\n        /* ############################################################## */\n        /**\n         The required UUID. It is set up with an initializer, and left alone.\n         */\n        let uuid = UUID()\n        \n        /* ############################################################## */\n        /**\n         This is where we will track our subscriptions.\n         */\n        var subscriptions: [RVS_GeneralObservableProtocol] = []\n\nBecause the protocol default works with an Array of references, this should be a `class`.\n\nSubscribing to an Observable is as simple as calling its [`subscribe()`](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/5978359d3521f125b565e63767328ceec911a170/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift#L134) method, with the observer, supplied:\n\n    let subscribedObserver = observableInstance.subscribe(observerInstance)\n\nThe response is the `observerInstance`, if the subscription was successful. This allows the method to be chained. It may be nil, if the observer is already subscribed.\n\nUnsubscribing is exactly the same, except that we call the [`unsubscribe()`](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/5978359d3521f125b565e63767328ceec911a170/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift#L145) method, this time.\n\n    let unsubscribedObserver = observableInstance.unsubscribe(observerInstance)\n\nThere are also `unsubscribeAll()` methods for [the Observable](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/5978359d3521f125b565e63767328ceec911a170/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift#L154), and for [the subscription-tracking Observer](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/5978359d3521f125b565e63767328ceec911a170/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift#L401).\n\nCalling these will remove every Observer from an Observable instance, or every Observable from an Observer instance.\n\n### Callbacks\n\nThere aren't any required callbacks in the protocols, but there are a few, very basic optional ones.\n\nThe Observer protocol has callbacks that are made at the time that a subscription is confirmed:\n\n    func subscribedTo(_ observable: RVS_GeneralObservableProtocol)\n\nand when an unsubscription is confirmed:\n\n    func unsubscribedFrom(_ observable: RVS_GeneralObservableProtocol)\n\nThe subscription-tracking protocol has [a couple of internal methods](https://github.com/RiftValleySoftware/RVS_GeneralObserver/blob/5978359d3521f125b565e63767328ceec911a170/Sources/RVS_GeneralObserver/RVS_GeneralObserver_Protocols.swift#L366) that aren't supposed to be used by conformant instances.\n\nThe Observable protocol has a single optional callback:\n\n    func observer(_ observer: RVS_GeneralObserverProtocol, didSubscribe: Bool)\n\nThis is called whenever an Observer subscribes or unsubscribes (the second argument indicates that).\n\nOnce you have set up the `class`es (or `struct`s), you can then use the `observers` property (Observable) or the `subscriptions` property (the subcriber-tracking variant of the Observer protocol) to access and interact with the various targets, recasting, as necessary.\n\nAll protocols have an `amISubscribed()` Boolean method, where you pass in an Observer (or Observable) instance to be tested to see if an Observer is subscribed to an Observable.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friftvalleysoftware%2Frvs_generalobserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Friftvalleysoftware%2Frvs_generalobserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friftvalleysoftware%2Frvs_generalobserver/lists"}