{"id":24123811,"url":"https://github.com/flowduino/eventdrivenswift","last_synced_at":"2025-08-10T19:24:59.281Z","repository":{"id":53874113,"uuid":"517641687","full_name":"Flowduino/EventDrivenSwift","owner":"Flowduino","description":"The most powerful Event-Driven Observer Pattern solution the Swift language has ever seen!","archived":false,"fork":false,"pushed_at":"2022-09-04T12:07:34.000Z","size":1478,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-10T17:53:41.847Z","etag":null,"topics":["event","event-driven","event-driven-architecture","eventdriven","eventdrivenarchitecture","events","ios","mac","macos","multi-threading","observation","observer","observerpattern","swift","thread","threading","threads"],"latest_commit_sha":null,"homepage":"https://flowduino.com","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/Flowduino.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["Flowduino"]}},"created_at":"2022-07-25T11:43:17.000Z","updated_at":"2023-12-07T08:47:40.000Z","dependencies_parsed_at":"2022-09-05T01:41:44.757Z","dependency_job_id":null,"html_url":"https://github.com/Flowduino/EventDrivenSwift","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FEventDrivenSwift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FEventDrivenSwift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FEventDrivenSwift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Flowduino%2FEventDrivenSwift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Flowduino","download_url":"https://codeload.github.com/Flowduino/EventDrivenSwift/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233484537,"owners_count":18683099,"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":["event","event-driven","event-driven-architecture","eventdriven","eventdrivenarchitecture","events","ios","mac","macos","multi-threading","observation","observer","observerpattern","swift","thread","threading","threads"],"created_at":"2025-01-11T13:18:17.139Z","updated_at":"2025-01-11T13:18:18.465Z","avatar_url":"https://github.com/Flowduino.png","language":"Swift","funding_links":["https://github.com/sponsors/Flowduino"],"categories":[],"sub_categories":[],"readme":"# Event-Driven Swift\n\n\u003cp\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Swift-5.1%2B-yellowgreen.svg?style=flat\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/iOS-13.0+-865EFC.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/iPadOS-13.0+-F65EFC.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/macOS-10.15+-179AC8.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/tvOS-13.0+-41465B.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/watchOS-6.0+-1FD67A.svg\" /\u003e\n    \u003cimg src=\"https://img.shields.io/badge/License-MIT-blue.svg\" /\u003e\n    \u003ca href=\"https://github.com/apple/swift-package-manager\"\u003e\n      \u003cimg src=\"https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://discord.gg/WXCcw532ne\"\u003e\n      \u003cimg src=\"https://img.shields.io/discord/878568176856731688?logo=Discord\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\nDecoupling 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.\n\nWith 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.\n\n*Event Driven Swift* is an extremely powerful library designed specifically to power your Event-Driven Applications in the Swift language.\n\n## Decoupled Topology\nWhere traditional software design principles would require communicating objects to reference each-other directly, Event-Driven design patterns eliminate the need for this.\n\n\u003cimg 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\"\u003e\n\n## Terminology\nUnderstanding 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.\n\n### Event\nAn *Event* is simply an immutable payload of information that can be used to drive logic and behaviour.\n\nThink 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*.\n\nIn `EventDrivenSwift`, we would typically define an *Event* as a `struct` conforming to the `Eventable` protocol.\n\nHere is a simple example:\n```swift\nstruct TemperatureEvent: Eventable {\n    var temperatureInCelsius: Float\n}\n```\nNote that the above example, `TemperatureEvent`, is the most basic example of an *Event* possible, in that it only contains one piece of information.\nIn reality, your *Events* can encapsulate as much information as is logical for a single cohesive operation trigger.\n\n**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*.\n\n### Event Queue\nAn *Event Queue* is a sequencial collection (`Array`) of `Eventable` objects that will automatically be processed whenever the *Queue* is not empty.\n*Queues* are always processed in the order First-in-First-out (or *FiFo*).\n\nNote that *Events* dispatched to a *Queue* will always be processed after *Events* dispatched to a *Stack*.\n\n### Event Stack\nAn *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*)\n\nNote that *Events* dispatched to a *Stack* will always be processed before *Events* dispatched to a *Queue*.\n\n### Event Priority\n*Events* can be dispatched to a *Queue* or *Stack* with one of the following *Priorities*:\n`.highest` will be processed first\n`.high` will be processed second\n`.normal` will be processed third\n`.low` will be processed fourth\n`.lowest` will be processed last\n\nThis means that we can enforce some degree of *execution order* over *Events* at the point of dispatch.\n\n### Dispatch\n*Dispatch* is a term comparable to *Broadcast*.\nWhen 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*.\n\nOnce 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.\"\n\n*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.\n\n### `EventThread`\nAn `EventThread` is a `class` inheriting the base type provided by this library called `EventThread`.\n\nBeneath the surface, `EventThread` descends from `Thread`, and is literally what is known as a `Persistent Thread`.\nThis 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.\n\nUnlike 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.\n\nOnce 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*.\n\n**Note:** any number of `EventThread`s can receive the same *Event*s. This means that you can process the same *Event* for any number of purposes, in any number of ways, with any number of outcomes.\n\n### Event Handler (or Callback)\nWhen 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.\n\nYou 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.\n\nThis makes it extremely easy to manage and maintain the *Event Subscriptions* that each `EventThread` has been implemented to process.\n\n## Performance-Centric\n`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*.\n\nWith 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 `EventThread`s through its own *Thread*.\n\nThis means that there is a near-zero wait time between instructing an *Event* to *Dispatch*, and continuing on in the invoking Thread's execution.\n\nDespite 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!\n\n## Built on `Observable`\n`EventDrivenSwift` is built on top of our [`Observable` library](https://github.com/flowduino/observable), and `EventThread` descends from `ObservableThread`, meaning that it supports full *Observer Pattern* behaviour as well as *Event-Driven* behaviour.\n\nPut simply: you can Observe `EventThread`s anywhere in your code that it is necessary, including from SwiftUI Views.\n\nThis 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.\n\n## Built on `ThreadSafeSwift`\n`EventDrivenSwift` is also built on top of our [`ThreadSafeSwift` library](https://github.com/flowduino/threadsafeswift), and every public method and member of every type in `EventDrivenSwift` is designed specifically to be *Thread-Safe*.\n\nIt 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.\n\n## Installation\n### Xcode Projects\nSelect `File` -\u003e `Swift Packages` -\u003e `Add Package Dependency` and enter `https://github.com/Flowduino/EventDrivenSwift.git`\n\n### Swift Package Manager Projects\nYou can use `EventDrivenSwift` as a Package Dependency in your own Packages' `Package.swift` file:\n```swift\nlet package = Package(\n    //...\n    dependencies: [\n        .package(\n            url: \"https://github.com/Flowduino/EventDrivenSwift.git\",\n            .upToNextMajor(from: \"5.0.0\")\n        ),\n    ],\n    //...\n)\n```\n\nFrom there, refer to `EventDrivenSwift` as a \"target dependency\" in any of _your_ package's targets that need it.\n\n```swift\ntargets: [\n    .target(\n        name: \"YourLibrary\",\n        dependencies: [\n          \"EventDrivenSwift\",\n        ],\n        //...\n    ),\n    //...\n]\n```\nYou can then do `import EventDrivenSwift` in any code that requires it.\n\n## Usage\nSo, now that we've taken a look at what `EventDrivenSwift` is, what it does, and we've covered a lot of the important *Terminology*, let's take a look at how we can actually use it.\n\n### Defining an *Event* type\nYou can make virtually *any* `struct` type into an *Event* type simply by inheriting from `Eventable`:\n```swift\nstruct TemperatureEvent: Eventable {\n    var temperatureInCelsius: Float\n} \n```\nIt really is as simple as that!\n\n### *Dispatching* an *Event*\nNow that we have a defined *Event* type, let's look at how we would *Dispatch* an *Event* of this type:\n```swift\nlet temperatureEvent = TemperatureEvent(temperatureInCelsius: 23.25)\n```\nThe above creates an instance of our `TemperatureEvent` *Event* type.\nIf we want to dispatch it via a *Queue* with the `.normal` *Priority*, we can do so as easily as this:\n```swift\ntemperatureEvent.queue()\n```\nWe can also customise the *Priority*:\n```swift\ntemperatureEvent.queue(priority: .highest)\n```\nThe above would dispatch the Event via a *Stack* with the `.highest` *Priority*.\n\nThe same works when dispatching via a *Stack*:\n```swift\ntemperatureEvent.stack()\n```\nAbove would again be with `.normal` *Priority*...\n```swift\ntemperatureEvent.stack(priority: .highest)\n```\nAbove would be with `.highest` *Priority*.\n\n### Scheduled *Dispatching* of an *Event*\nVersion 4.2.0 introduced *Scheduled Dispatch* into the library:\n```swift\ntemperatureEvent.scheduleQueue(at: DispatchTime.now() + TimeInterval().advanced(by: 4), priority: .highest)\n```\nThe above would *Dispatch* the `temperatureEvent` after 4 seconds, via the *Queue*, with the *highest Priority*\n```swift\ntemperatureEvent.scheduleStack(at: DispatchTime.now() + TimeInterval().advanced(by: 4), priority: .highest)\n```\nThe above would *Dispatch* the `temperatureEvent` after 4 seconds, via the *Stack*, with the *highest Priority*\n\n*Scheduled Event Dispatch* is a massive advantage when your use-case requires a fixed or calculated time delay between the composition of an *Event*, and its *Dispatch* for processing. \n\n### (Receiving \u0026 Processing Events - Method 1) Defining an `EventThread`\nSo, we have an *Event* type, and we are able to *Dispatch* it through a *Queue* or a *Stack*, with whatever *Priority* we desire. Now we need a way to Receive our `*`TemperatureEvent`s so that we can do something with them. One way of doing this is to define an `EventThread` to listen for and process our `TemperatureEvent`s.\n\n```swift\nclass TemperatureProcessor: EventThread {\n    /// Register our Event Listeners for this EventThread\n    override func registerEventListeners() {\n        addEventCallback(onTemperatureEvent, forEventType: TemperatureEvent.self)\n    }\n    \n    /// Define our Callback Function to process received TemperatureEvent Events\n    func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n\n    }\n}\n```\nBefore we dig into the implementation of `onTemperatureEvent`, which can basically do whatever we would want to do with the data provided in the `TemperatureEvent`, let's take a moment to understand what is happening in the above code.\n\nFirstly, `TemperatureProcessor` inherits from `EventThread`, which is where all of the magic happens to receive *Events* and register our *Listeners* (or *Callbacks* or *Handlers*).\n\nThe function `registerEventListeners` will be called automatically when an instance of `TemperatureProcessor` is created. Within this method, we call `addEventCallback` to register `onTemperatureEvent` so that it will be invoked every time an *Event* of type `TemperatureEvent` is *Dispatched*.\n\nOur *Callback* (or *Handler* or *Listener Event*) is called `onTemperatureEvent`, which is where we will implement whatever *Operation* is to be performed against a `TemperatureEvent`.\n\nVersion 5.0.0 introduces the new parameter, `dispatchTime`, which will always provide the `DispatchTime` reference at which the *Event* was *Dispatched*. You can use this to determine *Delta* (how much time has passed since the *Event* was *Dispatched*), which is particularly useful if you are performing interpolation and/or extrapolation.\n\nNow, let's actually do something with our `TemperatureEvent` in the `onTemperatureEvent` method.\n```swift\n    /// An Enum to map a Temperature value onto a Rating\n    enum TemperatureRating: String {\n        case belowFreezing = \"Below Freezing\"\n        case freezing = \"Freezing\"\n        case reallyCold = \"Really Cold\"\n        case cold = \"Cold\"\n        case chilly = \"Chilly\"\n        case warm = \"Warm\"\n        case hot = \"Hot\"\n        case reallyHot = \"Really Hot\"\n        case boiling = \"Boiling\"\n        case aboveBoiling = \"Steam\"\n        \n        static func fromTemperature(temperatureInCelsius: Float) -\u003e TemperatureRating {\n            if temperatureInCelsius \u003c 0 { return .belowFreezing }\n            else if temperatureInCelsius == 0 { return .freezing }\n            else if temperatureInCelsius \u003c 5 { return .reallyCold }\n            else if temperatureInCelsius \u003c 10 { return .cold }\n            else if temperatureInCelsius \u003c 16 { return .chilly }\n            else if temperatureInCelsius \u003c 22 { return .warm }\n            else if temperatureInCelsius \u003c 25 { return .hot }\n            else if temperatureInCelsius \u003c 100 { return .reallyHot }\n            else if temperatureInCelsius == 100 { return .boiling }\n            else { return .aboveBoiling }\n        }\n    }\n    \n    @ThreadSafeSemaphore public var temperatureInCelsius: Float = Float.zero\n    @ThreadSafeSemaphore public var temperatureRating: TemperatureRating = .freezing\n    \n    func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n        temperatureInCelsius = event.temperatureInCelsius\n        temperatureRating = TemperatureRating.fromTemperature(event.temperatureInCelsius)\n    }\n}\n```\nThe above code is intended to be illustrative, rather than *useful*. Our `onTemperatureEvent` passes *Event*'s encapsulated `temperatureInCelsius` to a public variable (which could then be read by other code as necessary) as part of our `EventThread`, and also pre-calculates a `TemperatureRating` based on the Temperature value received in the *Event*.\n\nUltimately, your code can do whatever you wish with the *Event*'s *Payload* data!\n\n### Playground Code to test everything so far\nThe only thing you're missing so far is how to create an instance of your `EventListner` type.\nThis is in fact remarkably simple. The following can be run in a Playground:\n```swift\nlet temperatureProcessor = TemperatureProcessor()\n```\nThat's all you need to do to create an instance of your `TemperatureProcessor`.\n\nLet's add a line to print the inital values of `temperatureProcessor`:\n```swift\nprint(\"Temp in C: \\(temperatureProcessor.temperatureInCelsius)\")\nprint(\"Temp Rating: \\(temperatureProcessor.temperatureRating)\")\n```\n\nWe can now dispatch a `TemperatureEvent` to be processed by `temperatureProcessor`:\n```swift\nTemperatureEvent(temperatureInCelsius: 25.5).queue()\n```\n\nBecause *Events* are processed *Asynchronously*, and because this is just a Playground test, let's add a 1-second sleep to give `TemperatureProcessor` time to receive and process the *Event*. **Note:** In reality, this would need less than 1ms to process!\n```swift\nsleep(1)\n```\n\nNow let's print the same values again to see that they have changed:\n```swift\nprint(\"Temp in C: \\(temperatureProcessor.temperatureInCelsius)\")\nprint(\"Temp Rating: \\(temperatureProcessor.temperatureRating)\")\n```\n\nNow you have a little Playground code to visually confirm that your *Events* are being processed. You can modify this to see what happens.\n### Observing an `EventThread`\nRemember, `EventThread`s are also *Observable*, so we can not only receive and operate on *Events*, we can also notify *Observers* in response to *Events*.\n\nLet's take a look at a simple example based on the examples above.\nWe shall begin by defining an *Observer Protocol*:\n```swift\nprotocol TemperatureProcessorObserver: AnyObject {\n    func onTemperatureEvent(temperatureInCelsius: Float)\n}\n```\nNow let's modify the `onTemperatureEvent` method we implemented in the previous example:\n```swift\n    func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n        temperatureInCelsius = event.temperatureInCelsius\n        temperatureRating = TemperatureRating.fromTemperature(event.temperatureInCelsius)\n        \n        /// Notify any Observers...\n        withObservers { (observer: TemperatureProcessorObserver) in\n            observer.onTemperatureEvent(temperatureInCelsius: event.temperatureInCelsius)\n        }\n    }\n```\nNow, every time a `TemperatureEvent` is processed by the `EventThread`, it will also notify any direct *Observers* as well.\n\nIt should be noted that this functionality serves as a *complement* to *Event-Driven* behaviour, as there is no \"one size fits all\" solution to every requirement in software. It is often neccessary to combine methodologies to achieve the best results.\n\n### Reciprocal Events\nTypically, systems not only *consume* information, but also *return* information (results). This is not only true when it comes to Event-Driven systems, but also trivial to achieve.\n\nLet's expand upon the previous example once more, this time emitting a reciprocal *Event* to encapsulate the Temperature, as well as the `TemperatureRating` we calculated in response to the `TemperatureEvent`.\n\nWe'll begin by defining the Reciprocal *Event* type:\n```swift\nenum TemperatureRatingEvent: Eventable {\n    var temperatureInCelsius: Float\n    var temperatureRating: TemperatureRating\n}\n```\nWith the *Event* type defined, we can now once more expand our `onTemperatureEvent` to *Dispatch* our reciprocal `TemperatureRatingEvent`:\n```swift\n    func onTemperatureEvent(_ event: TemperatureEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n        temperatureInCelsius = event.temperatureInCelsius\n        temperatureRating = TemperatureRating.fromTemperature(event.temperatureInCelsius)\n        \n        /// Notify any Observers...\n        withObservers { (observer: TemperatureProcessorObserver) in\n            observer.onTemperatureEvent(temperatureInCelsius: event.temperatureInCelsius)\n        }\n        \n        /// Dispatch our Reciprocal Event...\n        TemperatureRatingEvent(\n            temperatureInCelsius = temperatureInCelsius,\n            temperatureRating = temperatureRating\n        ).queue()\n    }\n``` \nAs you can see, we can create and *Dispatch* an *Event* in a single operation. This is because *Events* should be considered to be \"fire and forget\". You need only retain a copy of the *Event* within the *Dispatching Method* if you wish to use its values later in the same operation. Otherwise, just create it and *Dispatch* it together, as shown above.\n\nNow that we've walked through these basic Usage Examples, see if you can produce your own `EventThread` to process `TemperatureRatingEvent`s. Everything you need to achieve this has already been demonstrated in this document.\n\n## `UIEventThread`\nVersion 2.0.0 introduced the `UIEventThread` base class, which operates exactly the same way as `EventThread`, with the notable difference being that your registered *Event* Callbacks will **always** be invoked on the `MainActor` (or \"UI Thread\"). You can simply inherit from `UIEventThread` instead of `EventThread` whenever it is imperative for one or more *Event* Callbacks to execute on the `MainActor`.\n\n## (Receiving \u0026 Processing Events - Method 2) `EventListener`\nVersion 3.0.0 introduced the `EventListener` concept to the Library. These are a universally-available means (available in any `class` you define) of *Receiving Events* dispatched from anywhere in your code, and require *considerably less code* to use.\n\nAn `EventListener` is a universal way of subscribing to *Events*, anywhere in your code, without having to define and operate within the constraints of an `EventThread`.\n\nBy design, `EventDrivenSwift` provides a *Central Event Listener*, which is automatically initialized should any of your code register a *Listener* for an *Event* by reference to the `Eventable` type.\n\n**Important Note:** `EventListener` will (by default) invoke the associated `Callbacks` on the same Thread (or `DispatchQueue`) from whence the *Listener* registered! This is an extremely useful behaviour, because it means that *Listeners* registered from the `MainActor` (or \"UI Thread\") will always execute on that Thread, with no additional overhead or code required by you.\n\nLet's register a simple *Listener* in some arbitrary `class`. For this example, let's produce a hypothetical *View Model* that will *Listen* for `TemperatureRatingEvent`, and would invalidate an owning `View` to show the newly-received values.\n\nFor the sake of this example, let's define this the pure SwiftUI way, *without* taking advantage of our `Observable` library: \n```swift\nclass TemperatureRatingViewModel: ObservableObject {\n    @Published var temperatureInCelsius: Float\n    @Published var temperatureRating: TemperatureRating\n    \n    var listenerHandle: EventListenerHandling?\n    \n    internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n        temperatureInCelsius = event.temperatureInCelsius\n        temperatureRating = event.temperatureRating\n    }\n    \n    init() {\n        // Let's register our Event Listener Callback!\n        listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent)\n    }\n}\n```\nIt really is **that** simple!\n\nWe can use a direct reference to an `Eventable` type, and invoke the `addListener` method, automatically bound to all `Eventable` types, to register our *Listener*.\n\nIn the above example, whenever the *Reciprocal Event* named `TemperatureRatingEvent` is dispatched, the `onTemperatureRatingEvent` method of any `TemperatureRatingViewModel` instance(s) will be invoked, in the context of that *Event*!\n\nDon't worry about managing the lifetime of your *Listener*! If the object which owns the *Listener* is destroyed, the *Listener* will be automatically unregistered for you!\n\nIf you need your *Event Callback* to execute on the *Listener's* Thread, as of Version 3.1.0... you can!\n```swift\nlistenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .listenerThread)\n```\n**Remember:** When executing an *Event Callback* on `.listenerThread`, you will need to ensure that your *Callback* and all resources that it uses are 100% Thread-Safe!\n**Important:** Executing the *Event Callback* on `.listnerThread` can potentially delay the invocation of other *Event Callbacks*. Only use this option when it is necessary.\n\nYou can also execute your *Event Callback* on an ad-hoc `Task`:\n```swift\nlistenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .taskThread)\n```\n**Remember:** When executing an *Event Callback* on `.taskThread`, you will need to ensure that your *Callback* and all resources that it uses are 100% Thread-Safe!\n\nAnother thing to note about the above example is the `listenerHandle`. Whenever you register a *Listener*, it will return an `EventListenerHandling` object. You can use this value to *Unregister* your *Listener* at any time:\n```swift\n    listenerHandle.remove()\n```\nThis will remove your *Listener Callback*, meaning it will no longer be invoked any time a `TemperatureRatingEvent` is *Dispatched*.\n\n**Note:** This is an improvement for Version 4.1.0, as opposed to the use of an untyped `UUID` from previous versions.\n\n`EventListener`s are an extremely versatile and very powerful addition to `EventDrivenSwift`.\n\n## `EventListener` with *Latest-Only* Interest\nVersion 4.3.0 of this library introduces the concept of *Latest-Only Listeners*. A *Latest-Only Listener* is a *Listener* that will only be invoked for the very latest *Event* of its requested *Event Type*. If there are a number of older *Events* of this type pending in a Queue/Stack, they will simply be skipped over... and only the very *Latest* will invoke your *Listener*.\n\nWe have made it incredibly simple for you to configure your *Listener* to be a *Latest-Only Listener*. Taking the previous code example, we can simply modify it as follows:\n```swift\nclass TemperatureRatingViewModel: ObservableObject {\n    @Published var temperatureInCelsius: Float\n    @Published var temperatureRating: TemperatureRating\n    \n    var listenerHandle: EventListenerHandling?\n    \n    internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n        temperatureInCelsius = event.temperatureInCelsius\n        temperatureRating = event.temperatureRating\n    }\n    \n    init() {\n        // Let's register our Event Listener Callback!\n        listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, interestedIn: .latestOnly)\n    }\n}\n```\nBy including the `interestedIn` optional parameter when invoking `addListener` against any `Eventable` type, and passing for this parameter a value of `.latestOnly`, we define that this *Listener* is only interested in the *Latest* `TemperatureRatingEvent` to be *Dispatched*. Should a number of `TemperatureRatingEvent`s build up in the Queue/Stack, the above-defined *Listener* will simply discard any older Events, and only invoke for the newest.\n\n## `EventListener` with *Maximum Age* Interest\nVersion 5.1.0 of this library introduces the concent of *Maximum Age Listeners*. A *Maximum Age Listener* is a *Listener* that will only be invoked for *Events* of its registered *Event Type* that are younger than a defined *Maximum Age*. Any *Event* older than the defined *Maximum Age* will be skipped over, while any *Event* younger will invoke your *Listener*.\n\nWe have made it simple for you to configure your *Listener* to define a *Maximum Age* interest. Taking the previous code example, we can simply modify it as follows:\n```swift\nclass TemperatureRatingViewModel: ObservableObject {\n    @Published var temperatureInCelsius: Float\n    @Published var temperatureRating: TemperatureRating\n    \n    var listenerHandle: EventListenerHandling?\n    \n    internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n        temperatureInCelsius = event.temperatureInCelsius\n        temperatureRating = event.temperatureRating\n    }\n    \n    init() {\n        // Let's register our Event Listener Callback!\n        listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, interestedIn: .youngerThan, maximumAge: 1 * 1_000_000_000)\n    }\n}\n```\nIn the above code example, `maximumAge` is a value defined in *nanoseconds*. With that in mind, `1 * 1_000_000_000` would be 1 second. This means that, any `TemperatureRatingEvent` older than 1 second would be ignored by the *Listener*, while any `TemperatureRatingEvent` *younger* than 1 second would invoke the `onTemperatureRatingEvent` method.\n\nThis functionality is very useful when the context of an *Event*'s usage would have a known, fixed expiry.\n\n## `EventListener` with *Custom Event Filtering* Interest\nVersion 5.2.0 of this library introduces the concept of *Custom Event Filtering* for *Listeners*.\n\nNow, when registering a *Listener* for an `Eventable` type, you can specify a `customFilter` *Callback* which, ultimately, returns a `Bool` where `true` means that the *Listener* is interested in the *Event*, and `false` means that the *Listener* is **not** interested in the *Event*.\n\nWe have made it simple for you to configure a *Custom Filter* for your *Listener*. Taking the previous code example, we can simply modify it as follows:\n```swift\nclass TemperatureRatingViewModel: ObservableObject {\n    @Published var temperatureInCelsius: Float\n    @Published var temperatureRating: TemperatureRating\n    \n    var listenerHandle: EventListenerHandling?\n    \n    internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) {\n        temperatureInCelsius = event.temperatureInCelsius\n        temperatureRating = event.temperatureRating\n    }\n    \n    internal func onTemperatureRatingEventFilter(_ event: TemperatureRatingEvent, _ priority: EventPriority, _ dispatchTime: DispatchTime) -\u003e Bool {\n        if event.temperatureInCelsius \u003e 50 { return false } // If the Temperature is above 50 Degrees, this Listener is not interested in it!\n        return true // If the Temperature is NOT above 50 Degrees, the Listener IS interested in it!\n    }\n    \n    init() {\n        // Let's register our Event Listener Callback!\n        listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, interestedIn: .custom, customFilter: onTemperatureRatingEventFilter)\n    }\n}\n```\nThe above code will ensure that the `onTemperatureRatingEvent` method is only invoked for a `TemperatureRatingEvent` where its `temperatureInCelsius` is less than or equal to 50 Degrees Celsius. Any `TemperatureRatingEvent` with a `temperatureInCelsius` greater than 50 will simply be ignored by this *Listener*. \n\n## `EventPool`\nVersion 4.0.0 introduces the extremely powerful `EventPool` solution, making it possible to create managed groups of `EventThread`s, where inbound *Events* will be directed to the best `EventThread` in the `EventPool` at any given moment.\n\n`EventDrivenSwift` makes it trivial to produce an `EventPool` for any given `EventThread` type.\n\nTo create an `EventPool` of our `TemperatureProcessor` example from earlier, we can use a single line of code:\n```swift\nvar temperatureProcessorPool = EventPool\u003cTemperatureProcessor\u003e(capacity: 5)\n```\nThe above example will create an `EventPool` of `TemperatureProcessor`s, with an initial *Capacity* of **5** instances. This means that your program can concurrently process **5** `TemperatureEvent`s.\nObviously, for a process so simple and quick to complete as our earlier example, it would not be neccessary to produce an `EventPool`, but you can adapt this example for your own, more complex and time-consuming, `EventThread` implementations to immediately parallelise them.\n\n`EventPool`s enable you to specify the most context-appropriate *Balancer* on initialization:\n```swift\nvar temperatureProcessorPool = EventPool\u003cTemperatureProcessor\u003e(capacity: 5, balancer: EventPoolRoundRobinBalancer())\n```\nThe above example would use the `EventPoolRoundRobinBalancer` implementation, which simply directs each inbound `Eventable` to the next `EventThread` in the pool, rolling back around to the first after using the final `EventThread` in the pool.\n\nThere is also another *Balancer* available in version 4.0.0: \n```swift\nvar temperatureProcessorPool = EventPool\u003cTemperatureProcessor\u003e(capacity: 5, balancer: EventPoolLowestLoadBalancer())\n```\nThe above example would use the `EventPoolLowestLoadBalancer` implementation, which simply directs each inbound `Eventable` to the `EventThread` in the pool with the lowest number of pending `Eventable`s in its own *Queue* and *Stack*.\n\n**NOTE:** When no `balancer` is declared, `EventPool` will use `EventPoolRoundRobinBalancer` by default.\n\n## Features Coming Soon\n`EventDrivenSwift` is an evolving and ever-improving Library, so here are lists of the features you can expect in future releases.\n\nVersion 5.1.0 (or 6.0.0 if interface-breaking changes are required):\n- **Event Pool Scalers** - Dynamic Scaling for `EventPool` instances will be fully-implemented (for the moment, no automatic Scaling will occur, and you cannot change the scale of an *Event Pool* once it has been initialised)\n\n## License\n\n`EventDrivenSwift` is available under the MIT license. See the [LICENSE file](./LICENSE) for more info.\n\n## Join us on Discord\n\nIf you require additional support, or would like to discuss `EventDrivenSwift`, Swift, or any other topics related to Flowduino, you can [join us on Discord](https://discord.gg/WXCcw532ne).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowduino%2Feventdrivenswift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflowduino%2Feventdrivenswift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflowduino%2Feventdrivenswift/lists"}