/EventingLibrary

A uniform API for message passing

Primary LanguageSwiftMIT LicenseMIT

EventingLibrary

Rx training wheels 🚲

platforms License

EventingLibrary is a lightweight observable framework that makes it simple for the developer. The interface closely resembles RxSwift on purpose. If you find that you need more power, then the upgrade path to Rx should be fairly straightforward.

Install

CocoaPods

pod "marksands/EventingLibrary"

Carthage

github "marksands/EventingLibrary"

Observable

Observables subscribe to streams of values.

import EventingLibrary

// expose Observables in your public interface
public protocol Tappable {
	var tapped: Observable<Void> { get }
}

Event

Events can subscribe to other Events and Observables as well as send values. Because Events are Observables, they can both send and receive values.

import EventingLibrary

let event = Event<Int>()
let disposeBag = DisposeBag()

// subscriptions create a stream of values over time
disposeBag += event.subscribe(on: { value in
    print("Got \(value)!")
})

// Send an event to all subscribers
event.on(3)

// Subscribe to streams with the side effect of consuming the previously sent value
disposeBag += event.subscribeWithCurrentValue { value in
    // value is 3 on subscription
}

// optionally dispose the stream
disposeBag.dispose()

Notifier

Notifier provides type safety for NotificationCenter and allows you to emit custom Swift objects through Notification's userInfo.

import EventingLibrary

let event = Notifier(Notification.Name("ViewStateChanged"))

// equivalent to NotificationCenter.default.post(name: name, object: ViewState.loading)
event.on(["key": ViewState.loading])

// Or subscribe to notifications and handle them with a closure
disposeBag += event.subscribe(on: { userInfo in
    guard let state = userInfo["key"] as? ViewState else { return }
    print("Got view state: \(state)!")
})

Disposable

Disposables are optional for Observabless and Notifiers. If your subscription is retained by a disposable, you may dispose() of the subscription to stop receiving events to that handler.

Observable Operators

Filter

Filter values sent by an Event by applying a predicate to each value.

import EventingLibrary

let event = Event<Int>()

// Creates an event that filters odd numbers
let onlyEvenNumbers: Event<Int> = event.filter { $0 % 2 == 0 }

Map

Transform the values sent by an Event by applying a function to each value.

import EventingLibrary

let event = Event<Int>()

// Map Int values to Strings from the event
let intToStringEvent: Event<String> = event.map { String($0) }

FlatMap

Transform the values sent by an Event by applying a function that returns an Observable that itself emits items.

import EventingLibrary

// Service layer
func authenticateUser(withCredentials credentials: AuthCredentials) -> Observable<User> {
    return Observable<User>.create { event in
        let task = URLSession.shared.dataTask(with: url(from: credentials), completionHandler: { data, _, _ in
            event.on(User(data: data))
        })
        task.resume()
        
        return DisposableAction {
            task.cancel()
        }
    }
}

// Call site
loginCredentials
    .flatMap { authenticateUser(withCredentials: $0) }
    .subscribe { user in
        print("Got authenticated user: \(user)!")
}

Merge

Combine multiple events into a single event by merging their emitted values.

import EventingLibrary

let event1 = Event<Int>()
let event2 = Event<Int>()

let mergedIntsEvent: Event<Int> = Observable.merge(event1, event2)

Combine

Combine the latest value from multiple events and emit their results as a single unit.

import EventingLibrary

let event1 = Event<Int>()
let event2 = Event<String>()

let combinedEvents: Event<(Int, String)> = Observable.combine(event1, event2)

Chain mutlipe operators to create powerful transformations.

struct AuthenticationCredentials {
    let email: String
    let password: String
}

let validAuthCredentials = Observable<AuthenticationCredentials>
    .combine(
        emailEvent.filter(isValidEmail),
        passwordEvent.filter(isValidPassword)
    ).map(AuthenticationCredentials.init)

validAuthCredentials.subscribe {
    service.authenticate($0)
}

FAQ

Should I use this?

  • Probably not. 🤷🏼‍♀️