{"id":18026704,"url":"https://github.com/ruiaaperes/receiver","last_synced_at":"2025-07-07T11:07:18.398Z","repository":{"id":62452672,"uuid":"110881890","full_name":"RuiAAPeres/Receiver","owner":"RuiAAPeres","description":"Swift µframework implementing the Observer pattern 📡","archived":false,"fork":false,"pushed_at":"2019-06-18T16:19:41.000Z","size":62,"stargazers_count":239,"open_issues_count":4,"forks_count":11,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-01T07:46:23.178Z","etag":null,"topics":["microframework","observable","observer","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/RuiAAPeres.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-15T20:17:32.000Z","updated_at":"2024-02-01T02:06:05.000Z","dependencies_parsed_at":"2022-11-01T23:45:50.967Z","dependency_job_id":null,"html_url":"https://github.com/RuiAAPeres/Receiver","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/RuiAAPeres/Receiver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReceiver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReceiver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReceiver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReceiver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RuiAAPeres","download_url":"https://codeload.github.com/RuiAAPeres/Receiver/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReceiver/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264067137,"owners_count":23552150,"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":["microframework","observable","observer","swift"],"created_at":"2024-10-30T08:07:45.514Z","updated_at":"2025-07-07T11:07:18.375Z","avatar_url":"https://github.com/RuiAAPeres.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Receiver\n\n\u003ca href=\"https://github.com/Carthage/Carthage\"\u003e\u003cimg src=\"https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/RuiAAPeres/Receiver#cocoapods\"\u003e\u003cimg src=\"https://img.shields.io/cocoapods/v/Receiver.svg?style=flat\"\u003e\u003c/a\u003e\n[![codecov](https://codecov.io/gh/RuiAAPEres/Receiver/branch/master/graph/badge.svg)](https://codecov.io/gh/RuiAAPeres/Receiver)\n[![Build Status](https://travis-ci.org/RuiAAPeres/Receiver.svg?branch=master)](https://travis-ci.org/RuiAAPeres/Receiver)\n[![Swift 4.1](https://img.shields.io/badge/Swift-4.1-orange.svg?style=flat)](https://developer.apple.com/swift/)\n[![License MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg?style=flat)](https://opensource.org/licenses/MIT)\n\n\n1. [Intro](https://github.com/RuiAAPeres/Receiver#intro)\n2. [🌈 Enter `Receiver`! 🌈](https://github.com/RuiAAPeres/Receiver#-enter-receiver-)\n3. [Adding as a Dependency 🚀](https://github.com/RuiAAPeres/Receiver#adding-as-a-dependency-)\n4. [Basic usage 😎](https://github.com/RuiAAPeres/Receiver#basic-usage-)\n5. [Operators 🤖](https://github.com/RuiAAPeres/Receiver#operators-)\n6. [Strategies](https://github.com/RuiAAPeres/Receiver#strategies)\n7. [Opinionated, in what way? 🤓](https://github.com/RuiAAPeres/Receiver#opinionated-in-what-way-)\n8. [Ok, so why would I use this? 🤷‍♀️](https://github.com/RuiAAPeres/Receiver#ok-so-why-would-i-use-this-️)\n\n\n## Intro\n\nAs a [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift) user myself, most of time, it's difficult to convince someone to just simply start using it. The reality, for better or worse, is that most projects/teams are not ready to adopt it:\n\n1. The intrinsic problems of adding a big dependency.\n2. The learning curve.\n3. Adapting the current codebase to a FRP mindset/approach.\n\nNevertheless, a precious pattern can still be used, even without such an awesome lib like [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift). 😖\n\n## 🌈 Enter `Receiver`! 🌈\n\n![](https://viralviralvideos.com/wp-content/uploads/GIF/2015/06/OMG-this-is-so-awesome-GIF.gif)\n\n`Receiver` is nothing more than an opinionated micro framework implementation of the [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (**~120 LOC**). Or, if you prefer, [`FRP`](https://en.wikipedia.org/wiki/Functional_reactive_programming) without the `F` and a really small `R` ([rP](https://en.wikipedia.org/wiki/Reactive_programming) 🤔).\n\n## Adding as a Dependency 🚀\n\n#### Carthage\n\nIf you use Carthage to manage your dependencies, simply add Receiver to your Cartfile:\n\n```\ngithub \"RuiAAPeres/Receiver\" ~\u003e 0.0.1\n```\n\nIf you use Carthage to build your dependencies, make sure you have added `Receiver.framework` to the \"Linked Frameworks and Libraries\" section of your target, and have included them in your Carthage framework copying build phase.\n\n#### CocoaPods\n\nIf you use CocoaPods to manage your dependencies, simply add `Receiver` to your Podfile:\n\n```\npod 'Receiver', '~\u003e 0.0.1'\n```\n\n## Basic usage 😎\n\nLet's begin with the basics. **There are three methods in total**. Yup, that's right.\n\n#### 1. Creating the Receiver\n\n```swift\nlet (transmitter, receiver) = Receiver\u003cInt\u003e.make()\n```\n\nA `receiver` can never be created without an associated `transmitter` (what good would that be?)\n\n#### 2. Listening to an event 📡\n\nThis is how you observe events:\n\n```swift\nreceiver.listen { cheezburgers in print(\"Can I haz \\(cheezburgers) cheezburger. 🐈\") }\n```\n\nAs expected, you can do so as many times as you want:\n\n```swift\nreceiver.listen { cheezburgers in print(\"Can I haz \\(cheezburgers) cheezburger. 🐈\") }\n\n\nreceiver.listen { cheezburgers in print(\"I have \\(cheezburgers) cheezburgers and you have none!\")}\n```\n\nAnd both handlers will be called, when an event is broadcasted. ⚡️\n\n#### 3. Broadcasting an event 📻\n\nThis is how you send events:\n\n```swift\ntransmitter.broadcast(1)\n```\n\n## Operators 🤖\n\nReceiver provides a set of operators akin to ReactiveSwift:\n\n* [`map`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L2#L26)\n* [`filter`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L28#L56)\n* [`withPrevious`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L58#L88)\n* [`take`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L129#L164)\n* [`skip`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L90#L127)\n* [`skipRepeats`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L167#L214)\n* [`skipNil`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L257#L290)\n* [`uniqueValues`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L217#L254)\n\n## Strategies\n\nIf you are familiar with FRP, you must have heard about [cold and hot semantics](http://codeplease.io/2017/10/15/ras-s1e3-3/) (if not don't worry! ☺️). `Receiver` provides all three flavours explicitly, when you initialize it, via `make(strategy:)`. By default, the `Receiver` is `.hot`.\n\n### `.cold` ❄️:\n\n```swift\nlet (transmitter, receiver) = Receiver\u003cInt\u003e.make(with: .cold)\ntransmitter.broadcast(1)\ntransmitter.broadcast(2)\ntransmitter.broadcast(3)\n\nreceiver.listen { wave in\n    // This will be called with `wave == 1`\n    // This will be called with `wave == 2`\n    // This will be called with `wave == 3`\n    // This will be called with `wave == 4`\n}\n\ntransmitter.broadcast(4)\n```\n\nInternally, the `Receiver` will keep a buffer of the previous sent values. Once there is a new listener, all the previous values are sent. When the `4` is sent, it will be \"listened to\" as expected.\n\n### `.warm(upTo: Int)` 🌈:\n\nThis strategy allows you to specify how big the buffer should be:\n\n```swift\nlet (transmitter, receiver) = Receiver\u003cInt\u003e.make(with: .warm(upTo: 1))\ntransmitter.broadcast(1)\ntransmitter.broadcast(2)\n\nreceiver.listen { wave in\n    // This will be called with `wave == 2`\n    // This will be called with `wave == 3`\n}\n\ntransmitter.broadcast(3)\n```\n\nIn this case `1` will never be called, because the limit specified (`upTo: 1`) is too low, so only `2` is kept in the buffer.\n\n### `.hot` 🔥:\n\n```swift\nlet (transmitter, receiver) = Receiver\u003cInt\u003e.make(with: .hot) // this is the default strategy\ntransmitter.broadcast(1)\ntransmitter.broadcast(2)\n\nreceiver.listen { wave in\n    // This will be called with `wave == 3`\n}\n\ntransmitter.broadcast(3)\n```\n\nAnything broadcasted before listening is discarded.\n\n## Opinionated, in what way? 🤓\n\n#### Initializer. 🌳\n\nThe `make` method, follows the same approach used in ReactiveSwift, with `pipe`. Since a `receiver` only makes sense with a `transmitter`, it's only logical for them to be created together.\n\n#### Separation between the reader and the writer. ⬆️ ⬇️\n\nA lot of libs have the reader and the writer bundled within the same entity. For the purposes and use cases of this lib, it makes sense to have these concerns separated. It's a bit like a `UITableView` and a `UITableViewDataSource`: one fuels the other, so it might be better for them to be split into two different entities.\n\n## Ok, so why would I use this? 🤷‍♀️\n\n~Well, to make your codebase awesome of course.~ There are a lot of places where the observer pattern can be useful. In the most simplistic scenario, when delegation is not good enough and you have an `1-to-N` relationship.\n\nA good use case for this would in tracking an `UIApplication`'s lifecycle:\n\n```swift\nenum ApplicationLifecycle {\n    case didFinishLaunching\n    case didBecomeActive\n    case didEnterBackground\n}\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    var window: UIWindow?\n    private var transmitter: Receiver\u003cApplicationLifecycle\u003e.Transmitter!\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -\u003e Bool {\n\n        let (transmitter, receiver) = Receiver\u003cApplicationLifecycle\u003e.make()\n        self.transmitter = transmitter\n        // Pass down the `receiver` to where it's needed (e.g. ViewModel, Controllers)\n\n        transmitter.broadcast(.didFinishLaunching)\n        return true\n    }\n\n    func applicationDidEnterBackground(_ application: UIApplication) {\n        transmitter.broadcast(.didEnterBackground)\n    }\n\n    func applicationDidBecomeActive(_ application: UIApplication) {\n        transmitter.broadcast(.didBecomeActive)\n    }\n}\n```\n\nSimilar to the `ApplicationLifecycle`, the same approach could be used for MVVM:\n\n\n```swift\nclass MyViewController: UIViewController {\n    private let viewModel: MyViewModel\n    private let transmitter: Receiver\u003cUIViewControllerLifecycle\u003e.Transmitter\n\n    init(viewModel: MyViewModel, transmitter: Receiver\u003cUIViewControllerLifecycle\u003e.Transmitter) {\n        self.viewModel = viewModel\n        self.transmitter = transmitter\n        super.init(nibName: nil, bundle: nil)\n    }\n\n    override func viewDidLoad() {\n        super.viewdDidLoad()\n        transmitter.broadcast(.viewDidLoad)\n    }\n\n    override func viewDidAppear(_ animated: Bool) {\n        super.viewDidAppear(animated)\n        transmitter.broadcast(.viewDidAppear)\n    }\n\n    override func viewDidDisappear(_ animated: Bool) {\n        super.viewDidDisappear(animated)\n        transmitter.broadcast(.viewDidDisappear)\n    }\n}\n```\n\nThe nice part is that the `UIViewController` is never aware of the `receiver`, as it should be. ✨\n\nAt initialization time:\n\n```swift\nlet (transmitter, receiver) = Receiver\u003cUIViewControllerLifecycle\u003e.make()\nlet viewModel = MyViewModel(with: receiver)\nlet viewController = MyViewController(viewModel: viewModel, transmitter: transmitter)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruiaaperes%2Freceiver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fruiaaperes%2Freceiver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruiaaperes%2Freceiver/lists"}