• Stars
    star
    239
  • Rank 168,763 (Top 4 %)
  • Language
    Swift
  • License
    MIT License
  • Created about 7 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Swift µframework implementing the Observer pattern 📡

Receiver

codecov Build Status Swift 4.1 License MIT

  1. Intro
  2. 🌈 Enter Receiver! 🌈
  3. Adding as a Dependency 🚀
  4. Basic usage 😎
  5. Operators 🤖
  6. Strategies
  7. Opinionated, in what way? 🤓
  8. Ok, so why would I use this? 🤷‍♀️

Intro

As a 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:

  1. The intrinsic problems of adding a big dependency.
  2. The learning curve.
  3. Adapting the current codebase to a FRP mindset/approach.

Nevertheless, a precious pattern can still be used, even without such an awesome lib like ReactiveSwift. 😖

🌈 Enter Receiver! 🌈

Receiver is nothing more than an opinionated micro framework implementation of the Observer pattern (~120 LOC). Or, if you prefer, FRP without the F and a really small R (rP 🤔).

Adding as a Dependency 🚀

Carthage

If you use Carthage to manage your dependencies, simply add Receiver to your Cartfile:

github "RuiAAPeres/Receiver" ~> 0.0.1

If 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.

CocoaPods

If you use CocoaPods to manage your dependencies, simply add Receiver to your Podfile:

pod 'Receiver', '~> 0.0.1'

Basic usage 😎

Let's begin with the basics. There are three methods in total. Yup, that's right.

1. Creating the Receiver

let (transmitter, receiver) = Receiver<Int>.make()

A receiver can never be created without an associated transmitter (what good would that be?)

2. Listening to an event 📡

This is how you observe events:

receiver.listen { cheezburgers in print("Can I haz \(cheezburgers) cheezburger. 🐈") }

As expected, you can do so as many times as you want:

receiver.listen { cheezburgers in print("Can I haz \(cheezburgers) cheezburger. 🐈") }


receiver.listen { cheezburgers in print("I have \(cheezburgers) cheezburgers and you have none!")}

And both handlers will be called, when an event is broadcasted. ⚡️

3. Broadcasting an event 📻

This is how you send events:

transmitter.broadcast(1)

Operators 🤖

Receiver provides a set of operators akin to ReactiveSwift:

Strategies

If you are familiar with FRP, you must have heard about cold and hot semantics (if not don't worry! ☺️). Receiver provides all three flavours explicitly, when you initialize it, via make(strategy:). By default, the Receiver is .hot.

.cold ❄️:

let (transmitter, receiver) = Receiver<Int>.make(with: .cold)
transmitter.broadcast(1)
transmitter.broadcast(2)
transmitter.broadcast(3)

receiver.listen { wave in
    // This will be called with `wave == 1`
    // This will be called with `wave == 2`
    // This will be called with `wave == 3`
    // This will be called with `wave == 4`
}

transmitter.broadcast(4)

Internally, 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.

.warm(upTo: Int) 🌈:

This strategy allows you to specify how big the buffer should be:

let (transmitter, receiver) = Receiver<Int>.make(with: .warm(upTo: 1))
transmitter.broadcast(1)
transmitter.broadcast(2)

receiver.listen { wave in
    // This will be called with `wave == 2`
    // This will be called with `wave == 3`
}

transmitter.broadcast(3)

In this case 1 will never be called, because the limit specified (upTo: 1) is too low, so only 2 is kept in the buffer.

.hot 🔥:

let (transmitter, receiver) = Receiver<Int>.make(with: .hot) // this is the default strategy
transmitter.broadcast(1)
transmitter.broadcast(2)

receiver.listen { wave in
    // This will be called with `wave == 3`
}

transmitter.broadcast(3)

Anything broadcasted before listening is discarded.

Opinionated, in what way? 🤓

Initializer. 🌳

The 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.

Separation between the reader and the writer. ⬆️ ⬇️

A 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.

Ok, so why would I use this? 🤷‍♀️

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.

A good use case for this would in tracking an UIApplication's lifecycle:

enum ApplicationLifecycle {
    case didFinishLaunching
    case didBecomeActive
    case didEnterBackground
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    private var transmitter: Receiver<ApplicationLifecycle>.Transmitter!

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        let (transmitter, receiver) = Receiver<ApplicationLifecycle>.make()
        self.transmitter = transmitter
        // Pass down the `receiver` to where it's needed (e.g. ViewModel, Controllers)

        transmitter.broadcast(.didFinishLaunching)
        return true
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        transmitter.broadcast(.didEnterBackground)
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        transmitter.broadcast(.didBecomeActive)
    }
}

Similar to the ApplicationLifecycle, the same approach could be used for MVVM:

class MyViewController: UIViewController {
    private let viewModel: MyViewModel
    private let transmitter: Receiver<UIViewControllerLifecycle>.Transmitter

    init(viewModel: MyViewModel, transmitter: Receiver<UIViewControllerLifecycle>.Transmitter) {
        self.viewModel = viewModel
        self.transmitter = transmitter
        super.init(nibName: nil, bundle: nil)
    }

    override func viewDidLoad() {
        super.viewdDidLoad()
        transmitter.broadcast(.viewDidLoad)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        transmitter.broadcast(.viewDidAppear)
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        transmitter.broadcast(.viewDidDisappear)
    }
}

The nice part is that the UIViewController is never aware of the receiver, as it should be.

At initialization time:

let (transmitter, receiver) = Receiver<UIViewControllerLifecycle>.make()
let viewModel = MyViewModel(with: receiver)
let viewController = MyViewController(viewModel: viewModel, transmitter: transmitter)

More Repositories

1

UIViewController-Swizzled

Used to print the structure of your application as you use it
Objective-C
847
star
2

Reactor

Powering your RAC architecture
Swift
184
star
3

OptionalExtensions

Swift µframework with extensions for the Optional Type
Swift
182
star
4

iOSArchitecture

Sample project showing a common architecture I use when creating iOS Projects
Objective-C
157
star
5

Swift-Sugar

Swift's Sugar. Heavily inspired on Objc Sugar(https://github.com/supermarin/ObjectiveSugar)
Swift
153
star
6

Tangerine

Swift µframework for fetching images 🍊
Swift
149
star
7

Rinku

A simple networking library
Swift
43
star
8

KirKos

Focus on the most important
Objective-C
34
star
9

SaferFonts

Avoiding potential mistypes while using fonts programatically in Swift
Swift
30
star
10

RPNSURLConnection-Swizzled

NSURLConnection's category for the request and stack trace output
Objective-C
26
star
11

RPDynamicWarningView

A simple warning view that takes advantage of UIKit Dynamics
Objective-C
22
star
12

NSURLConnection-Blocks

Category that allows the dev to specify blocks for the success and failure cases
Objective-C
13
star
13

UITextField-DelegationBlocks

Add blocks, as per a single instance basis, to your UITextFields
Objective-C
12
star
14

NSObject-Dealloc

Category used to know when your object has been deallocated
Objective-C
7
star
15

8472

Strava client, with focus activities analysis. 🏃‍♂️
Swift
6
star
16

JSaaS

Jaden Smith as a Service
Elixir
6
star
17

NSPortoWorkshop

A detailed description about a potential Workshop that could be organised in Porto, Portugal.
5
star
18

SliderController

Objective-C
5
star
19

fartlek

Strava Webhooks + APNS
Python
5
star
20

RPGallery

Gallery used to upload pictures
Objective-C
4
star
21

TeamGen

Generating balanced teams 🚀🏈
Swift
4
star
22

OctifyIssues

Used as Issues Tracker for the App Octo Alarm
3
star
23

FootballTeamGenerator

...because just random is not good enough
Swift
3
star
24

RuiAAPeres

2
star
25

ReversePolishNotation-Swift

Reverse Polish Notation done in Swift, based on http://learnyousomeerlang.com/functionally-solving-problems#rpn-calculator
Swift
2
star
26

but_the_android_team_uses_RxJava

this is a parody
Swift
2
star
27

OctifyPush

APNS for my iOS App (Octify)
Go
2
star
28

TestingFRP

Some benchmarks
Swift
2
star
29

SquareStock

Square Stock
Objective-C
1
star
30

Refactoring

Swift
1
star
31

Franz

Swift
1
star
32

Reading18

A list of material (articles + books) I read + wrote in 2018.
1
star
33

swiftAveiroFP

FP stuff with Argo Fun
Swift
1
star
34

principles

1
star
35

PromisesPlayground

Objective-C
1
star