EventDrivenSwift
The most powerful Event-Driven Observer Pattern solution the Swift language has ever seen!
Install / Use
/learn @Flowduino/EventDrivenSwiftREADME
Event-Driven Swift
<p> <img src="https://img.shields.io/badge/Swift-5.1%2B-yellowgreen.svg?style=flat" /> <img src="https://img.shields.io/badge/iOS-13.0+-865EFC.svg" /> <img src="https://img.shields.io/badge/iPadOS-13.0+-F65EFC.svg" /> <img src="https://img.shields.io/badge/macOS-10.15+-179AC8.svg" /> <img src="https://img.shields.io/badge/tvOS-13.0+-41465B.svg" /> <img src="https://img.shields.io/badge/watchOS-6.0+-1FD67A.svg" /> <img src="https://img.shields.io/badge/License-MIT-blue.svg" /> <a href="https://github.com/apple/swift-package-manager"> <img src="https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat" /> </a> <a href="https://discord.gg/WXCcw532ne"> <img src="https://img.shields.io/discord/878568176856731688?logo=Discord" /> </a> </p>Decoupling of discrete units of code contributes massively to the long-term maintainability of your project(s). While Observer Pattern is a great way of providing some degree of decoupling by only requiring that Observers conform to a mutually-agreed Interface (Protocol, in Swift), we can go significantly further using an Event-Driven Pattern.
With Event-Driven systems, there is absolutely no direct reference between discrete units of code. Instead, each discrete unit of code emits and listens for Events (as applicable). An Event is simply a structured object containing immutable information. Each unit of code can then operate based on the Event(s) it receives, and perform whatever operation(s) are necessary in the context of that particular unit of code.
Event Driven Swift is an extremely powerful library designed specifically to power your Event-Driven Applications in the Swift language.
Decoupled Topology
Where traditional software design principles would require communicating objects to reference each-other directly, Event-Driven design patterns eliminate the need for this.
<img src="/Diagrams/Event-Driven%20SwiftUI%20View.png?raw=true" alt="Topological Diagram showing Event-Driven ViewModel being updated via Events from the Data Model Repository" title="Topological Diagram showing Event-Driven ViewModel being updated via Events from the Data Model Repository">Terminology
Understanding the Terminology used in this Library and its supporting examples/documentation will aid you considerably in immediately leveraging these tools to produce extremely powerful, high-performance, entirely-decoupled and easily maintained Event-Driven solutions.
Event
An Event is simply an immutable payload of information that can be used to drive logic and behaviour.
Think of an Event as being akin to an Operation Trigger. In response to receiving an Event of a known Type, a distinct unit of code would perform an appropriate operation based on the information received in that Event's payload.
In EventDrivenSwift, we would typically define an Event as a struct conforming to the Eventable protocol.
Here is a simple example:
struct TemperatureEvent: Eventable {
var temperatureInCelsius: Float
}
Note that the above example, TemperatureEvent, is the most basic example of an Event possible, in that it only contains one piece of information.
In reality, your Events can encapsulate as much information as is logical for a single cohesive operation trigger.
Important Note: An Event should never include any Reference-Type values (such as class instances). Events need to be immutable, meaning that none of their values can possibly change after they have been Dispatched.
Event Queue
An Event Queue is a sequencial collection (Array) of Eventable objects that will automatically be processed whenever the Queue is not empty.
Queues are always processed in the order First-in-First-out (or FiFo).
Note that Events dispatched to a Queue will always be processed after Events dispatched to a Stack.
Event Stack
An Event Stack is virtually the same as an Event Queue, except that it is processed in the opposite order: Last-in-First-out (or LiFo)
Note that Events dispatched to a Stack will always be processed before Events dispatched to a Queue.
Event Priority
Events can be dispatched to a Queue or Stack with one of the following Priorities:
.highest will be processed first
.high will be processed second
.normal will be processed third
.low will be processed fourth
.lowest will be processed last
This means that we can enforce some degree of execution order over Events at the point of dispatch.
Dispatch
Dispatch is a term comparable to Broadcast.
When we Dispatch an Event, it means that we are sending that information to every EventThread (see next section) that is listening for that Event type.
Once an Event has been Dispatched, it cannot be cancelled or modified. This is by design. Think of it as saying that "you cannot unsay something once you have said it."
Events can be Dispatched from anywhere in your code, regardless of what Thread is invoking it. In this sense, Events are very much a fire and forget process.
EventThread
An EventThread is a class inheriting the base type provided by this library called EventThread.
Beneath the surface, EventThread descends from Thread, and is literally what is known as a Persistent Thread.
This means that the Thread would typically exist either for a long as your particular application would require it, or even for the entire lifetime of your application.
Unlike most Threads, EventThread has been built specifically to operate with the lowest possible system resource footprint. When there are no Events waiting to be processed by your EventThread, the Thread will consume absolutely no CPU time, and effectively no power at all.
Once your EventThread receives an Event of an Eventable type to which it has subscribed, it will wake up automatically and process any waiting Events in its respective Queue and Stack.
Note: any number of EventThreads can receive the same Events. This means that you can process the same Event for any number of purposes, in any number of ways, with any number of outcomes.
Event Handler (or Callback)
When you define your EventThread descendant, you will implement a function called registerEventListeners. Within this function (which is invoked automatically every time an Instance of your EventThread descendant type is initialised) you will register the Eventable Types to which your EventThread is interested; and for each of those, define a suitable Handler (or Callback) method to process those Events whenever they occur.
You will see detailed examples of this in the Usage section of this document later, but the key to understand here is that, for each Eventable type that your EventThread is interested in processing, you will be able to register your Event Handler for that Event type in a single line of code.
This makes it extremely easy to manage and maintain the Event Subscriptions that each EventThread has been implemented to process.
Performance-Centric
EventDrivenSwift is designed specifically to provide the best possible performance balance both at the point of Dispatching an Event, as well as at the point of Processing an Event.
With this in mind, EventDrivenSwift provides a Central Event Dispatch Handler by default. Whenever you Dispatch an Event through either a Queue or Stack, it will be immediately enqueued within the Central Event Dispatch Handler, where it will subsequently be Dispatched to all registered EventThreads through its own Thread.
This means that there is a near-zero wait time between instructing an Event to Dispatch, and continuing on in the invoking Thread's execution.
Despite using an intermediary Handler in this manner, the time between Dispatch of an Event and the Processing of that Event by each EventThread is impressively short! This makes EventDrivenSwift more than useful for performance-critical applications, including videogames!
Built on Observable
EventDrivenSwift is built on top of our Observable library, and EventThread descends from ObservableThread, meaning that it supports full Observer Pattern behaviour as well as Event-Driven behaviour.
Put simply: you can Observe EventThreads anywhere in your code that it is necessary, including from SwiftUI Views.
This means that your application can dynamically update your Views in response to Events being received and processed, making your application truly and fully multi-threaded, without you having to produce code to handle the intra-Thread communication yourself.
Built on ThreadSafeSwift
EventDrivenSwift is also built on top of our ThreadSafeSwift library, and every public method and member of every type in EventDrivenSwift is designed specifically to be Thread-Safe.
It is strongly recommended that your own implementations using EventDrivenSwift adhere strictly to the best Thread-Safe standards. With that said, unless you are defining a var or func that is accessible publicly specifically for the purpose of displaying information on the UI, most back-end implemenations built with a pure Event-Driven methodology will not need to concern themselves too much with Thread-Safety.
Installation
Xcode Projects
Select File -> Swift Packages -> Add Package Dependency and enter https://github.com/Flowduino/EventDrivenSwift.git
Swift Package Manager Projects
You can use EventDrivenSwift as a Package Dependency in your own Packages' Package.swift file:
let package = Package(
//...
dependencies: [
.package(
url: "https://github.com/Flowduino/EventDrivenSwift.gi
