A simple thread-safe, in-memory bus for Swift that supports fully-typed Events and States.

Overview

build status macOS iOS Linux Apache 2

Causality

Causality is a simple thread-safe, in-memory bus for Swift that supports fully-typed Events and States.

In addition, Causality has provisions for monitoring State information. State is similar to Event, but differ in that:

  • State handlers will be called immediately with the last known good value (if one is available)
  • State handlers will not be called if the state value is identical to the previous value
  • Whereas an Event has an associated Message, a State has an associated Value.
  • A state's Value must conform to the Equatable protocol.

Installation

Swift Package Manager

Add the Causality package to the dependencies within your application's Package.swift file. Substitute "x.y.z" with the latest Causality release.

.package(url: "https://github.com/dannys42/Causality.git", from: "x.y.z")

Add Causality to your target's dependencies:

.target(name: "example", dependencies: ["Causality"]),

Cocoapods

Add Causality to your Podfile:

pod `Causality`

Usage

Events

Just an event (no data)

The simplest event to manage has no associated data.

Declare Events

This declares an event called aTriggerEvent that has no associated data.

struct MyEvents {
    static let aTriggerEvent = Causality.Event<Causality.NoMessage>(label: "A Trigger")
}

Subscribe to events

To subscribe to this event:

let subscription = Causality.bus.subscribe(MyEvents.aTriggerEvent) { _ in
    print("Event was triggered")
}

Publish events

To publish/post an event of this type:

Causality.bus.publish(MyEvents.aTriggerEvent)

An event with associated data

Events can include data of any type (referred to as a Message). The event label is fully type specified with the message. So a subscriber will have a fully typed message available to its handler.

Define the Message

A message can be a standard Swift type like Int, String, etc. Or it can be a struct or class that conform to Causality.Message. Take care as to whether you want value or reference semantics for messages. Generally, value semantics (i.e. a struct) will be safer. In this example, we'll declare a struct:

struct InterestingMessage: Causality.Message {
    let string: String
    let number: Int
}

Declare the event

Events may be declared with an associated Message. If declared, the Message is a required typed parameter for publishing an event. And similarly it will be supplied as a typed parameter to subscribers of the event.

Declaring an event with a message:

let MyInterestingEvent = Causality.Event<SomeMessage>(label: "Some Event")
let MyStringEvent = Causality.Event<String>(label: "An event with a String message")
let MyNumberEvent = Causality.Event<Int>(label: "An event with an Int message")

Or categorize your events:

struct MyEvents {
    static let MyInterestingEvent = Causality.Event<InterestingMessage>(label: "An interesting Event 1")
    static let MyStringEvent = Causality.Event<String>(label: "An event with a String message")
    static let MyNumberEvent = Causality.Event<Int>(label: "An event with an Int message")
}

Subscribing and Unsubscribing to the events

Save your subscriptions to unsubscribe later:

let subscription = Causality.bus.subscribe(MyEvents.MyInterestingEvent) { interestingMessage in
    print("A message from MyInterestingEvent: \(interestingMessage)")
}

Casaulity.bus.unsubscribe(subscription)

Or unsubscribe from within a subscription handler. Here's an example of a one-shot event handler:

Causality.bus.subscribe(MyEvents.MyStringEvent) { subscription, string in
    print("A string from MyStringEvent: \(string)")
    
    subscription.unsubscribe()
}

Publish events

To publish/post an event:

Causality.bus.publish(MyEvents.MyInterestingEvent, 
    message: InterestingMessage(string: "Hello", number: 42))

Event Buses

Bus Alias

Create aliases for your bus:

let eventBus = Causality.bus

eventBus.publish(MyEvents.MyInterestingEvent, 
    message: InterestingMessage(string: "Hello", number: 42))

Local Buses

Or create local buses to isolate your events:

let newEventBus = Causality.Bus(label: "My local bus")

newEventBus.publish(MyEvents.interestingEvent, 
    message: InterestingMessage(string: "Hello", number: 42))

State

Define the State Value

Similar to an Event, a State has an associated Value. Values can be raw types such as Int, String, etc. Or they may be struct or a class. (Similar to a an event Message, you'll usually want to use a struct.) However a Value must conform to Equatable.

struct PlayerInfo: Causality.StateValue {
    let numberOfLives: Int
    let health: Int
    let armor: Int
}

Declare the state

Declaring a state with the associated value:

let playerState = Causality.State<PlayerInfo>(label: "Player State")

Or categorize your states:

struct GameStates {
    static let playerState1 = Causality.State<PlayerInfo>(label: "Player 1 State")
    static let playerState2 = Causality.State<PlayerInfo>(label: "Player 2 State")
}

Subscribing and Unsubscribing to State changes

Save your subscriptions to unsubscribe later:

let subscription = Causality.bus.subscribe(GameStates.playerState1) { state in
    print("Player 1 state changed to: \(state)")
}

Casaulity.bus.unsubscribe(subscription)

Or unsubscribe from within a subscription handler. This example will monitor only a single state change:

Causality.bus.subscribe(GameStates.playerState1) { subscription, message in
    print("Player 1 state changed to: \(state)")
    
    subscription.unsubscribe()
}

If the state was previously set, the subscription handler will be called immediately with the last known value. The subscription handler will only be called if subsequent .set() calls have differing values.

Setting State

Causality.bus.set(GameStates.playerState1, 
    value: PlayerInfo(numberOfLives: 3, health: 75, armor: 10))

Dynamic States

In the game example above, we have one Causality.State variable for every state. But what if we have "n" number of players? In that case, we can use Dynamic States. Dynamic States allows you to parameterize your State. Dynamic States are Codable and require you to define CodingKeys and to overload the encode() function to specify "key" parameters. These parameters are used to uniquely identify the state. For example:

class PlayerState<Value: PlayerInfo>: Causality.DynamicState<Value> {
    let playerId: Int

    init(playerId: Int) {
        self.playerId = playerId
    }
    enum CodingKeys: CodingKey {
        case playerId
    }
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.playerId, forKey: .playerId)
    }
}

Now to subscribe:

Causality.bus.subscribe(PlayerState<PlayerInfo>(playerId: 1)) { subscription, playerInfo in
    print("Current player info is: \(playerInfo))
}

Or to add more organization:

struct GameState {
    static func playerState(_ playerId: Int) -> PlayerState<PlayerInfo> {
        return PlayerState<PlayerInfo>(playerId: playerId)
    }
}

Causality.bus.subscribe(GameState.playerState(1)) { subscription, playerInfo in
    print("Current player info is: \(playerInfo)")
}

And to set/update a state:

Causality.bus.set(state: GameState.playerState(1), 
                  value: PlayerInfo(
                            numberOfLines: 3,
                            health: 75,
                            armor: 100))

Similarly, you could use base types of Int, String, etc. for the Value.

let UserNameState = Causality.State<String>(label: "user name state")
Causality.bus.subscribe(UserNameState) { username in
    print("Username is now: \(username)")
}
Causality.bus.set(UserNameState, "Mary Jane Doe")

Dynamic Events

Events can be parameterized by defining them in a similar way:

class MyEvent<Message: Causality.Message>: Causality.DynamicEvent<Message> {

    let eventId: Int

    init(eventId: Int) {
        self.eventId = eventId
    }
    enum CodingKeys: CodingKey {
        case eventId
    }
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.eventId, forKey: .eventId)
    }
}

Then create the event:

struct MyEvents {
    static func event(_ eventId: Int) -> MyEvent<InterestingMessage> {
        return MyEvent<InterestingMessage>(eventId: eventId)
    }
}

Subscribe to the event:

let subscription = Causality.bus.subscribe(MyEvents.event(1)) { subscription, message in
    print("A message from event \(subscription.event.eventId): \(message)")
}

And publish events:

Causality.bus.publish(MyEvents.event(1), 
    message: InterestingMessage(string: "Hello", number: 42))

API Documentation

For more information visit our API reference.

Related Projects

License

This library is licensed under Apache 2.0. The full license text is available in LICENSE.

You might also like...
Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU.
Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU.

Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU. Features Thread safe: Implement by dispatch_sema

A lightweight and efficient bus tracker app for the Miami-Dade Transit System
A lightweight and efficient bus tracker app for the Miami-Dade Transit System

A lightweight bus tracker app for the Miami-Dade Transit System Built in Swift, this app features a favorites page, real-time bus location and ETA, us

Contains the swift rewrite of Find My Bus NJ iOS App
Contains the swift rewrite of Find My Bus NJ iOS App

FIND MY BUS NJ 2 An app for tracking NJ Transit bus times. Dependancies Alamofire SwiftyJSON PKHUD Fabric Getting started Install fastlane and imagema

A minimalistic, thread safe, non-boilerplate and super easy to use version of Active Record on Core Data.
A minimalistic, thread safe, non-boilerplate and super easy to use version of Active Record on Core Data.

Skopelos A minimalistic, thread-safe, non-boilerplate and super easy to use version of Active Record on Core Data. Simply all you need for doing Core

A declarative, thread safe, and reentrant way to define code that should only execute at most once over the lifetime of an object.

SwiftRunOnce SwiftRunOnce allows a developer to mark a block of logic as "one-time" code – code that will execute at most once over the lifetime of an

A thread safe throttle written in Swift

SwiftThrottle - A thread safe throttle written in Swift licensed under MIT. Introduction This throttle is intended to prevent the program from crashing

Type-safe thread-local storage in Swift
Type-safe thread-local storage in Swift

Threadly is a Swift µframework that allows for type-safe thread-local storage. What is Thread-Local Storage? Thread-local storage (TLS) lets you defin

OTAtomics - Multi-platform Swift thread-safe atomics library

OTAtomics Multi-platform Swift thread-safe atomics library. The library has full

A barebone, thread-safe Redux-like container for Swift.

SwiftTinyRedux SwiftTinyRedux is a barebone, thread-safe Redux-like container for Swift. It features a minimal API and supports composable reducers. I

💾 Safe, statically-typed, store-agnostic key-value storage written in Swift!

Storez 💾 Safe, statically-typed, store-agnostic key-value storage Highlights Fully Customizable: Customize the persistence store, the KeyType class,

💾 Safe, statically-typed, store-agnostic key-value storage written in Swift!

Storez 💾 Safe, statically-typed, store-agnostic key-value storage Highlights Fully Customizable: Customize the persistence store, the KeyType class,

A thread safe, performant, feature rich image fetcher
A thread safe, performant, feature rich image fetcher

PINRemoteImage Fast, non-deadlocking parallel image downloader and cache for iOS PINRemoteImageManager is an image downloading, processing and caching

Remote shell using libssh2 with Objective-C, thread safe implementation.

NSRemoteShell Remote shell using libssh2 with Objective-C. Thread safe implementation. Available as Swift Package. git libssh2 prebuilt binaries are r

Thread -safe access to a lazily retrieved value, with optional validity checking

SerialUpdatingValue Thread-safe access to a lazily retrieved value, with optional validity checking Motivation Swift's Structured Concurrency provides

An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms.
An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms.

KeyValueStorage An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms. Supported Platforms iOS macOS watc

A declarative, performant, iOS calendar UI component that supports use cases ranging from simple date pickers all the way up to fully-featured calendar apps.
A declarative, performant, iOS calendar UI component that supports use cases ranging from simple date pickers all the way up to fully-featured calendar apps.

HorizonCalendar A declarative, performant, calendar UI component that supports use cases ranging from simple date pickers all the way up to fully-feat

PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a consistent user interface for common content states (i.e. loading, loaded, empty, and error).
PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a consistent user interface for common content states (i.e. loading, loaded, empty, and error).

PJFDataSource PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a co

YoutubeKit is a video player that fully supports Youtube IFrame API and YoutubeDataAPI for easily create a Youtube app
YoutubeKit is a video player that fully supports Youtube IFrame API and YoutubeDataAPI for easily create a Youtube app

YoutubeKit YoutubeKit is a video player that fully supports Youtube IFrame API and YoutubeDataAPI to easily create Youtube applications. Important Ref

:octocat: AdaptiveController is a 'Progressive Reduction' Swift UI module for adding custom states to Native or Custom iOS UI elements. Swift UI component by @Ramotion
:octocat: AdaptiveController is a 'Progressive Reduction' Swift UI module for adding custom states to Native or Custom iOS UI elements. Swift UI component by @Ramotion

ADAPTIVE TAB BAR 'Progressive Reduction' module for adding custom states to Native or Custom UI elements. We specialize in the designing and coding of

A fully customizable library to easily display Animated Toast Messages in iOS using Swift!

CustomToastView-swift A fully customizable library to easily display Animated Toast Messages in iOS using Swift! Preview - All the custom toasts you c

Leticia Rodriguez 13 Aug 20, 2022
APNSUtil is makes code simple using apple push notification service

APNSUtil APNSUtil makes code simple settings and landing for apple push notification service. Features Using apple push notification service simply No

Steve Kim 30 Mar 24, 2022
OS X and iOS application and framework to play with the Apple Push Notification service (APNs)

Pusher OS X and iOS application and framework to play with the Apple Push Notification service (APNs) Installation Install the Mac app using Homebrew

noodlewerk 6.2k Dec 22, 2022
Easily create Local Notifications in swift - Wrapper of UserNotifications Framework

In IOS 10, apple updated their library for Notifications and separated Local and push notifications to a new framework: User Notifications This librar

Devesh Laungani 208 Dec 14, 2022
A Swift Library for Dynamic Island Push Notification.

Push Notification With Dynamic Island Handle Push Notification with Dynamic Island ?? Descreption: Since there is not library for Apple Push Notificat

Amir Diafi 5 Nov 12, 2022
MemoryCache - type-safe, thread-safe memory cache class in Swift

MemoryCache is a memory cache class in swift. The MemoryCache class incorporates LRU policies, which ensure that a cache doesn’t

Yusuke Morishita 74 Nov 24, 2022
🚎 Simple type-safe event bus implementation in swift

?? RealEventsBus RealEventsBus is a small swift experiment package to implement a basic type-safe event bus mechanism. Some other implementations in G

Daniele Margutti 12 Jul 19, 2022
Swiftui-pressed-states-example - Examples of Pressed States in SwiftUI

Examples of Pressed States in SwiftUI pressed-states.mp4

Philip Davis 6 Nov 15, 2022
Modern thread-safe and type-safe key-value observing for Swift and Objective-C

Now Archived and Forked PMKVObserver will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork o

Postmates Inc. 708 Jun 29, 2022
A Swift event bus for UIWebView/WKWebView and JS.

An event bus for sending messages between UIWebView/WKWebView and embedded JS. Made with pure Swift. Features Easy, fast and reliable event bus system

Coshx 149 Oct 9, 2022