Skip to content

Commit eeb980f

Browse files
committed
Version 2.0.0
- 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
1 parent 8de59bf commit eeb980f

File tree

6 files changed

+174
-18
lines changed

6 files changed

+174
-18
lines changed

README.md

+8-11
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ let package = Package(
125125
dependencies: [
126126
.package(
127127
url: "https://github.com/Flowduino/EventDrivenSwift.git",
128-
.upToNextMajor(from: "1.1.0")
128+
.upToNextMajor(from: "2.0.0")
129129
),
130130
],
131131
//...
@@ -189,13 +189,13 @@ Above would be with `.highest` *Priority*.
189189
### Defining an `EventReceiver`
190190
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.
191191

192+
**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)
193+
192194
```swift
193195
class TemperatureProcessor: EventReceiver {
194196
/// Register our Event Listeners for this EventReceiver
195197
override func registerEventListeners() {
196-
addEventCallback({ event, priority in
197-
self.callTypedEventCallback(self.onTemperatureEvent, forEvent: event, priority: priority)
198-
}, forEventType: TemperatureEvent.self)
198+
addEventCallback(onTemperatureEvent, forEventType: TemperatureEvent.self)
199199
}
200200

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

211211
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*.
212212

213-
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`
214-
215213
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`.
216214

217-
**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.
218-
For the moment, this solution works well, and enables you to begin using `EventDrivenSwift` in your applications immediately.
219-
220215
Now, let's actually do something with our `TemperatureEvent` in the `onTemperatureEvent` method.
221216
```swift
222217
/// An Enum to map a Temperature value onto a Rating
@@ -350,12 +345,14 @@ As you can see, we can create and *Dispatch* an *Event* in a single operation. T
350345

351346
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.
352347

348+
## `UIEventReceiver`
349+
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`.
350+
353351
## Features Coming Soon
354352
`EventDrivenSwift` is an evolving and ever-improving Library, so here is a list of the features you can expect in future releases:
355353
- **Event Pools** - A superset expanding upon a given `EventReceiver` descendant type to provide pooled processing based on given scaling rules and conditions.
356-
- **`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.
357354

358-
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*.
355+
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*.
359356

360357
## License
361358

Sources/EventDrivenSwift/EventReceiver/EventReceiver.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,18 @@ open class EventReceiver: EventHandler, EventReceivable {
6060
/**
6161
Registers an Event Callback for the given `Eventable` Type
6262
- Author: Simon J. Stuart
63-
- Version: 1.0.0
63+
- Version: 2.1.0
6464
- Parameters:
6565
- callback: The code to invoke for the given `Eventable` Type
6666
- forEventType: The `Eventable` Type for which to Register the Callback
6767
*/
68-
internal func addEventCallback(_ callback: @escaping EventCallback, forEventType: Eventable.Type) {
68+
internal func addEventCallback<TEvent: Eventable>(_ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type) {
6969
let eventTypeName = String(reflecting: forEventType)
7070

7171
_eventCallbacks.withLock { eventCallbacks in
72-
eventCallbacks[eventTypeName] = callback
72+
eventCallbacks[eventTypeName] = { event, priority in
73+
self.callTypedEventCallback(callback, forEvent: event, priority: priority)
74+
}
7375
}
7476

7577
/// We automatically register the Listener with the Central Event Dispatcher
@@ -79,7 +81,7 @@ open class EventReceiver: EventHandler, EventReceivable {
7981
/**
8082
Performs a Transparent Type Test, Type Cast, and Method Call via the `callback` Closure.
8183
- Author: Simon J. Stuart
82-
- Version: 1.0.0
84+
- Version: 2.1.0
8385
- Parameters:
8486
- callback: The code (Closure or Callback Method) to execute for the given `forEvent`, typed generically using `TEvent`
8587
- forEvent: The instance of the `Eventable` type to be processed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// UIEventReceivable.swift
3+
// Copyright (c) 2022, Flowduino
4+
// Authored by Simon J. Stuart on 11th August 2022
5+
//
6+
// Subject to terms, restrictions, and liability waiver of the MIT License
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
Protocol describing anything that Receives Events on the UI Thread
13+
- Author: Simon J. Stuart
14+
- Version: 2.1.0
15+
- Note: Inherits from `EventReceivable`
16+
*/
17+
public protocol UIEventReceivable: AnyObject, EventReceivable {
18+
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// UIEventReceiver.swift
3+
// Copyright (c) 2022, Flowduino
4+
// Authored by Simon J. Stuart on 11th August 2022
5+
//
6+
// Subject to terms, restrictions, and liability waiver of the MIT License
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
Abstract Base Type for all `UIEventRecevier` Thread Types.
13+
- Author: Simon J. Stuart
14+
- Version: 2.1.0
15+
- 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)
16+
- Note: Your Event Handlers/Listeners/Callbacks will be executed on the UI Thread every time.
17+
*/
18+
open class UIEventReceiver: EventReceiver, UIEventReceivable {
19+
override internal func callTypedEventCallback<TEvent: Eventable>(_ callback: @escaping TypedEventCallback<TEvent>, forEvent: Eventable, priority: EventPriority) {
20+
Task { /// Have to use a Task because this method is not `async`
21+
await MainActor.run { /// Forces the call to be invoked on the `MainActor` (UI Thread)
22+
super.callTypedEventCallback(callback, forEvent: forEvent, priority: priority)
23+
}
24+
}
25+
}
26+
}

Tests/EventDrivenSwiftTests/BasicEventReceiverTests.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ final class BasicEventReceiverTests: XCTestCase {
2525
}
2626

2727
override func registerEventListeners() {
28-
addEventCallback({ event, priority in
29-
self.callTypedEventCallback(self.eventOneCallback, forEvent: event, priority: priority)
30-
}, forEventType: TestEventTypeOne.self)
28+
addEventCallback(self.eventOneCallback, forEventType: TestEventTypeOne.self)
3129
}
3230
}
3331

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//
2+
// UIEventReceiverTests.swift.swift
3+
//
4+
//
5+
// Created by Simon Stuart on 11/08/2022.
6+
//
7+
8+
import XCTest
9+
import ThreadSafeSwift
10+
@testable import EventDrivenSwift
11+
12+
final class UIEventReceiverTests_swift: XCTestCase {
13+
struct TestEventTypeOne: Eventable {
14+
var foo: Int
15+
}
16+
17+
class TestEventThread: UIEventReceiver {
18+
@ThreadSafeSemaphore var foo: Int = 0
19+
20+
internal func eventOneCallback(_ event: TestEventTypeOne, _ priority: EventPriority) {
21+
foo = event.foo
22+
}
23+
24+
override func registerEventListeners() {
25+
addEventCallback(self.eventOneCallback, forEventType: TestEventTypeOne.self)
26+
}
27+
}
28+
29+
let expectedTestOneFoo: Int = 1000
30+
31+
/**
32+
I need to find a way of Unit Testing the `UIEventReceiver`.
33+
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)
34+
*/
35+
36+
/*
37+
func testEventDispatchQueueDirect() throws {
38+
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
39+
let eventThread = TestEventThread() // Create the Thread
40+
41+
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
42+
43+
eventThread.queueEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
44+
45+
sleep(5)
46+
47+
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
48+
}
49+
50+
func testEventDispatchQueueCentral() throws {
51+
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
52+
let eventThread = TestEventThread() // Create the Thread
53+
54+
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
55+
EventCentral.queueEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
56+
57+
sleep(5)
58+
59+
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
60+
}
61+
62+
func testEventDispatchQueueTransparent() throws {
63+
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
64+
let eventThread = TestEventThread() // Create the Thread
65+
66+
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
67+
68+
testOne.queue() // Now let's dispatch our Event to change this value
69+
70+
sleep(5)
71+
72+
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
73+
}
74+
75+
func testEventDispatchStackDirect() throws {
76+
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
77+
let eventThread = TestEventThread() // Create the Thread
78+
79+
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
80+
81+
eventThread.stackEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
82+
83+
sleep(5)
84+
85+
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
86+
}
87+
88+
func testEventDispatchStackCentral() throws {
89+
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
90+
let eventThread = TestEventThread() // Create the Thread
91+
92+
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
93+
94+
EventCentral.stackEvent(testOne, priority: .normal) // Now let's dispatch our Event to change this value
95+
96+
sleep(5)
97+
98+
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
99+
}
100+
101+
func testEventDispatchStackTransparent() throws {
102+
let testOne = TestEventTypeOne(foo: expectedTestOneFoo) // Create the Event
103+
let eventThread = TestEventThread() // Create the Thread
104+
105+
XCTAssertEqual(eventThread.foo, 0, "Expect initial value of eventThread.foo to be 0, but it's \(eventThread.foo)")
106+
107+
testOne.stack() // Now let's dispatch our Event to change this value
108+
109+
sleep(5)
110+
111+
XCTAssertEqual(eventThread.foo, testOne.foo, "Expect new value of eventThread.foo to be \(testOne.foo), but it's \(eventThread.foo)")
112+
}
113+
*/
114+
}

0 commit comments

Comments
 (0)