Skip to content

Commit

Permalink
Version 2.0.0
Browse files Browse the repository at this point in the history
- Introduced `UIEventReceiver`
- Eliminated the need to implement a boilerplate Closure to invoke `callTypedEventCallback` in order to type-qualify an `Eventable` for a Callback call. This is now done internally for you.
- Updated the README.MD file to reflect the above
  • Loading branch information
LK-Simon committed Aug 11, 2022
1 parent 8de59bf commit eeb980f
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 18 deletions.
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ let package = Package(
dependencies: [
.package(
url: "https://github.com/Flowduino/EventDrivenSwift.git",
.upToNextMajor(from: "1.1.0")
.upToNextMajor(from: "2.0.0")
),
],
//...
Expand Down Expand Up @@ -189,13 +189,13 @@ Above would be with `.highest` *Priority*.
### Defining an `EventReceiver`
So, 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 to define an `EventReceiver` to listen for and process our `TemperatureEvent`s.

**Note:** Code example in this section was updated for Version 2.0.0 due to considerable improvements, which necessitated changing the Interface slightly (for the better)

```swift
class TemperatureProcessor: EventReceiver {
/// Register our Event Listeners for this EventReceiver
override func registerEventListeners() {
addEventCallback({ event, priority in
self.callTypedEventCallback(self.onTemperatureEvent, forEvent: event, priority: priority)
}, forEventType: TemperatureEvent.self)
addEventCallback(onTemperatureEvent, forEventType: TemperatureEvent.self)
}

/// Define our Callback Function to process received TemperatureEvent Events
Expand All @@ -210,13 +210,8 @@ Firstly, `TemperatureProcessor` inherits from `EventReceiver`, which is where al

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

Notice that we use a *Closure* which invokes `self.callTypedEventCallback`. This is to address a fundamental limitation of Generics in the Swift language, and acts as a decorator to perform the Type Checking and Casting of the received `event` to the explicit *Event* type we expect. In this case, that is `TemperatureEvent`

Our *Callback* (or *Handler* or *Listener Event*) is called `onTemperatureEvent`, which is where we will implement whatever *Operation* is to be performed against a `TemperatureEvent`.

**Note**: The need to provide type checking and casting (in `onTemperatureEvent`) is intended to be a temporary requirement. We are looking at ways to decorate this internally within the library, so that we can reduce the amount of boilerplate code you have to produce in your implementations.
For the moment, this solution works well, and enables you to begin using `EventDrivenSwift` in your applications immediately.

Now, let's actually do something with our `TemperatureEvent` in the `onTemperatureEvent` method.
```swift
/// An Enum to map a Temperature value onto a Rating
Expand Down Expand Up @@ -350,12 +345,14 @@ As you can see, we can create and *Dispatch* an *Event* in a single operation. T

Now that we've walked through these basic Usage Examples, see if you can produce your own `EventReceiver` to process `TemperatureRatingEvent`s. Everything you need to achieve this has already been demonstrated in this document.

## `UIEventReceiver`
Version 2.0.0 introduces the `UIEventReceiver` base class, which operates exactly the same way as `EventReciever`, 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 `UIEventReceiver` instead of `EventReceiver` whenever it is imperative for one or more *Event* Callbacks to execute on the `MainActor`.

## Features Coming Soon
`EventDrivenSwift` is an evolving and ever-improving Library, so here is a list of the features you can expect in future releases:
- **Event Pools** - A superset expanding upon a given `EventReceiver` descendant type to provide pooled processing based on given scaling rules and conditions.
- **`UIEventReceiver`** - Will enable you to register Event Listener Callbacks to be executed on the UI Thread. This is required if you wish to use Event-Driven behaviour to directly update SwiftUI Views, for example.

These are the features intended for the next Release, which will either be *1.2.0* or *2.0.0* depending on whether these additions require interface-breaking changes to the interfaces in version *1.1.0*.
These are the features intended for the next Release, which will either be *2.1.0* or *3.0.0* depending on whether these additions require interface-breaking changes to the interfaces in version *2.0.0*.

## License

Expand Down
10 changes: 6 additions & 4 deletions Sources/EventDrivenSwift/EventReceiver/EventReceiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,18 @@ open class EventReceiver: EventHandler, EventReceivable {
/**
Registers an Event Callback for the given `Eventable` Type
- Author: Simon J. Stuart
- Version: 1.0.0
- Version: 2.1.0
- Parameters:
- callback: The code to invoke for the given `Eventable` Type
- forEventType: The `Eventable` Type for which to Register the Callback
*/
internal func addEventCallback(_ callback: @escaping EventCallback, forEventType: Eventable.Type) {
internal func addEventCallback<TEvent: Eventable>(_ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type) {
let eventTypeName = String(reflecting: forEventType)

_eventCallbacks.withLock { eventCallbacks in
eventCallbacks[eventTypeName] = callback
eventCallbacks[eventTypeName] = { event, priority in
self.callTypedEventCallback(callback, forEvent: event, priority: priority)
}
}

/// We automatically register the Listener with the Central Event Dispatcher
Expand All @@ -79,7 +81,7 @@ open class EventReceiver: EventHandler, EventReceivable {
/**
Performs a Transparent Type Test, Type Cast, and Method Call via the `callback` Closure.
- Author: Simon J. Stuart
- Version: 1.0.0
- Version: 2.1.0
- Parameters:
- callback: The code (Closure or Callback Method) to execute for the given `forEvent`, typed generically using `TEvent`
- forEvent: The instance of the `Eventable` type to be processed
Expand Down
19 changes: 19 additions & 0 deletions Sources/EventDrivenSwift/UIEventReceiver/UIEventReceivable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// UIEventReceivable.swift
// Copyright (c) 2022, Flowduino
// Authored by Simon J. Stuart on 11th August 2022
//
// Subject to terms, restrictions, and liability waiver of the MIT License
//

import Foundation

/**
Protocol describing anything that Receives Events on the UI Thread
- Author: Simon J. Stuart
- Version: 2.1.0
- Note: Inherits from `EventReceivable`
*/
public protocol UIEventReceivable: AnyObject, EventReceivable {

}
26 changes: 26 additions & 0 deletions Sources/EventDrivenSwift/UIEventReceiver/UIEventReceiver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// UIEventReceiver.swift
// Copyright (c) 2022, Flowduino
// Authored by Simon J. Stuart on 11th August 2022
//
// Subject to terms, restrictions, and liability waiver of the MIT License
//

import Foundation

/**
Abstract Base Type for all `UIEventRecevier` Thread Types.
- Author: Simon J. Stuart
- Version: 2.1.0
- Note: Inherit from this to implement a discrete unit of code designed specifically to operate upon specific `Eventable` types containing information useful to its operation(s)
- Note: Your Event Handlers/Listeners/Callbacks will be executed on the UI Thread every time.
*/
open class UIEventReceiver: EventReceiver, UIEventReceivable {
override internal func callTypedEventCallback<TEvent: Eventable>(_ callback: @escaping TypedEventCallback<TEvent>, forEvent: Eventable, priority: EventPriority) {
Task { /// Have to use a Task because this method is not `async`
await MainActor.run { /// Forces the call to be invoked on the `MainActor` (UI Thread)
super.callTypedEventCallback(callback, forEvent: forEvent, priority: priority)
}
}
}
}
4 changes: 1 addition & 3 deletions Tests/EventDrivenSwiftTests/BasicEventReceiverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ final class BasicEventReceiverTests: XCTestCase {
}

override func registerEventListeners() {
addEventCallback({ event, priority in
self.callTypedEventCallback(self.eventOneCallback, forEvent: event, priority: priority)
}, forEventType: TestEventTypeOne.self)
addEventCallback(self.eventOneCallback, forEventType: TestEventTypeOne.self)
}
}

Expand Down
114 changes: 114 additions & 0 deletions Tests/EventDrivenSwiftTests/UIEventReceiverTests.swift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// UIEventReceiverTests.swift.swift
//
//
// Created by Simon Stuart on 11/08/2022.
//

import XCTest
import ThreadSafeSwift
@testable import EventDrivenSwift

final class UIEventReceiverTests_swift: XCTestCase {
struct TestEventTypeOne: Eventable {
var foo: Int
}

class TestEventThread: UIEventReceiver {
@ThreadSafeSemaphore var foo: Int = 0

internal func eventOneCallback(_ event: TestEventTypeOne, _ priority: EventPriority) {
foo = event.foo
}

override func registerEventListeners() {
addEventCallback(self.eventOneCallback, forEventType: TestEventTypeOne.self)
}
}

let expectedTestOneFoo: Int = 1000

/**
I need to find a way of Unit Testing the `UIEventReceiver`.
It works, this I know, but Unit Tests operate on the UI Thread, which means they are blocking the `UIEventReceiver` callback until *after* the Test Method has already returned (thus failed)
*/

/*
func testEventDispatchQueueDirect() throws {
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
let eventThread = TestEventThread() // Create the Thread
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
eventThread.queueEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
sleep(5)
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
}
func testEventDispatchQueueCentral() throws {
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
let eventThread = TestEventThread() // Create the Thread
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
EventCentral.queueEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
sleep(5)
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
}
func testEventDispatchQueueTransparent() throws {
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
let eventThread = TestEventThread() // Create the Thread
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
testOne.queue() // Now let's dispatch our Event to change this value
sleep(5)
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
}
func testEventDispatchStackDirect() throws {
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
let eventThread = TestEventThread() // Create the Thread
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
eventThread.stackEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
sleep(5)
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
}
func testEventDispatchStackCentral() throws {
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
let eventThread = TestEventThread() // Create the Thread
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
EventCentral.stackEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
sleep(5)
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
}
func testEventDispatchStackTransparent() throws {
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
let eventThread = TestEventThread() // Create the Thread
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
testOne.stack() // Now let's dispatch our Event to change this value
sleep(5)
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
}
*/
}

0 comments on commit eeb980f

Please sign in to comment.