Skip to content

Commit c1600f1

Browse files
authored
Merge pull request #3 from Flowduino/better-event-callbacks
Better event callbacks
2 parents 05531f7 + 8fdcefc commit c1600f1

File tree

15 files changed

+459
-51
lines changed

15 files changed

+459
-51
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ class TemperatureRatingViewModel: ObservableObject {
370370
@Published var temperatureInCelsius: Float
371371
@Published var temperatureRating: TemperatureRating
372372

373-
var listenerToken: UUID
373+
var listenerHandle: EventListenerHandling?
374374

375375
internal func onTemperatureRatingEvent(_ event: TemperatureRatingEvent, _ priority: EventPriority) {
376376
temperatureInCelsius = event.temperatureInCelsius
@@ -379,7 +379,7 @@ class TemperatureRatingViewModel: ObservableObject {
379379

380380
init() {
381381
// Let's register our Event Listener Callback!
382-
listenerToken = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent)
382+
listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent)
383383
}
384384
}
385385
```
@@ -393,22 +393,24 @@ Don't worry about managing the lifetime of your *Listener*! If the object which
393393

394394
If you need your *Event Callback* to execute on the *Listener's* Thread, as of Version 3.1.0... you can!
395395
```swift
396-
listenerToken = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .listenerThread)
396+
listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .listenerThread)
397397
```
398398
**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!
399399
**Important:** Executing the *Event Callback* on `.listnerThread` can potentially delay the invocation of other *Event Callbacks*. Only use this option when it is necessary.
400400

401401
You can also execute your *Event Callback* on an ad-hoc `Task`:
402402
```swift
403-
listenerToken = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .taskThread)
403+
listenerHandle = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .taskThread)
404404
```
405405
**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!
406406

407-
Another thing to note about the above example is the `listenerToken`. Whenever you register a *Listener*, it will return a Unique Universal ID (a `UUID`) value. You can use this value to *Unregister* your *Listener* at any time:
407+
Another 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:
408408
```swift
409-
TemperatureRatingEvent.removeListener(listenerToken)
409+
listenerHandle.remove()
410410
```
411-
This way, when an *Event* is no longer relevant to your code, you can simply call `removeListener` against the `Eventable` type, and pass in the token returned when you added the *Listener* in the first place.
411+
This will remove your *Listener Callback*, meaning it will no longer be invoked any time a `TemperatureRatingEvent` is *Dispatched*.
412+
413+
**Note:** This is an improvement for Version 4.1.0, as opposed to the use of an untyped `UUID` from previous versions.
412414

413415
`EventListener`s are an extremely versatile and very powerful addition to `EventDrivenSwift`.
414416

Sources/EventDrivenSwift/Central/EventCentral.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ final public class EventCentral: EventDispatcher, EventCentralable {
7474
}
7575
}
7676

77-
@discardableResult @inline(__always) public static func addListener<TEvent>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread) -> UUID where TEvent : Eventable {
77+
@discardableResult @inline(__always) public static func addListener<TEvent>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling where TEvent : Eventable {
7878
return _shared.eventListener.addListener(requester, callback, forEventType: forEventType, executeOn: executeOn)
7979
}
8080

Sources/EventDrivenSwift/Central/EventCentralable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public protocol EventCentralable {
6868
- forEventType: The `Eventable` Type for which to Register the Callback
6969
- Returns: A `UUID` value representing the `token` associated with this Event Callback
7070
*/
71-
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn) -> UUID
71+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn) -> EventListenerHandling
7272

7373
/**
7474
Locates and removes the given Listener `token` (if it exists) from the Central Event Listener

Sources/EventDrivenSwift/Event/Eventable.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public protocol Eventable {
4343
- callback: The code to invoke for the given `Eventable` Type
4444
- Returns: A `UUID` value representing the `token` associated with this Event Callback
4545
*/
46-
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn) -> UUID
46+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn) -> EventListenerHandling
47+
48+
// @discardableResult static func addListener(_ requester: AnyObject?, _ eventType: any Eventable.Type, _ callback: @escaping TypedEventCallback<any Eventable.Type>, executeOn: ExecuteEventOn) -> UUID
4749

4850
/**
4951
Locates and removes the given Listener `token` (if it exists) from the Central Event Listener
@@ -80,11 +82,12 @@ extension Eventable {
8082
EventCentral.stackEvent(self, priority: priority)
8183
}
8284

83-
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread) -> UUID {
85+
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling {
8486
return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn)
8587
}
86-
88+
8789
public static func removeListener(_ token: UUID) {
8890
EventCentral.removeListener(token, typeOf: Self.self)
8991
}
9092
}
93+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// EventMethod.swift
3+
// Copyright (c) 2022, Flowduino
4+
// Authored by Simon J. Stuart on 21st August 2022
5+
//
6+
// Subject to terms, restrictions, and liability waiver of the MIT License
7+
//
8+
9+
import Foundation
10+
11+
public typealias EventMethodTypedEventCallback<TOwner: AnyObject, TEvent: Any> = (_ sender: TOwner, _ event: TEvent, _ priority: EventPriority) -> ()
12+
13+
/**
14+
Any Property wrapped with `EventMethod` will automatically conform to `EventMethodContainer`
15+
- Author: Simon J. Stuart
16+
- Version: 4.1.0
17+
- Note: This is used to conformity-test decorated `var`s to automatically register Event Listeners
18+
*/
19+
public protocol EventMethodContainer {
20+
associatedtype TEventType: Eventable
21+
associatedtype TOwner: AnyObject
22+
23+
var wrappedValue: EventMethodTypedEventCallback<TOwner, TEventType>? { get set }
24+
25+
mutating func prepare(owner: AnyObject)
26+
}
27+
28+
/**
29+
Decorate Typed Event Callback Closures as `var` with `@EventMethod<TEventType>` to automatically register them.
30+
- Author: Simon J. Stuart
31+
- Version: 4.1.0
32+
*/
33+
@propertyWrapper
34+
public struct EventMethod<TOwner: AnyObject, TEventType: Eventable>: EventMethodContainer {
35+
public var wrappedValue: EventMethodTypedEventCallback<TOwner, TEventType>?
36+
public var executeOn: ExecuteEventOn
37+
38+
private weak var owner: AnyObject? = nil
39+
40+
private func callback(event: TEventType, priority: EventPriority) {
41+
if let typedOwner = owner as? TOwner {
42+
wrappedValue?(typedOwner, event, priority)
43+
}
44+
}
45+
46+
public init(wrappedValue: EventMethodTypedEventCallback<TOwner, TEventType>?, executeOn: ExecuteEventOn = .requesterThread) {
47+
self.wrappedValue = wrappedValue
48+
self.executeOn = executeOn
49+
}
50+
51+
mutating public func prepare(owner: AnyObject) {
52+
if let typedOwner = owner as? TOwner {
53+
self.owner = owner
54+
TEventType.addListener(
55+
typedOwner,
56+
callback,
57+
executeOn: executeOn)
58+
}
59+
}
60+
}

Sources/EventDrivenSwift/EventHandler/EventHandler.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ open class EventHandler: ObservableThread, EventHandling {
127127
var eventStacks = [EventPriority:[any Eventable]]()
128128
_stacks.withLock { stacks in
129129
eventStacks = stacks
130-
stacks.removeAll(keepingCapacity: true)
130+
stacks.removeAll()
131131
}
132132

133133
for priority in EventPriority.inOrder {
@@ -147,7 +147,7 @@ open class EventHandler: ObservableThread, EventHandling {
147147
var eventQueues = [EventPriority:[any Eventable]]()
148148
_queues.withLock { queues in
149149
eventQueues = queues
150-
queues.removeAll(keepingCapacity: true)
150+
queues.removeAll()
151151
}
152152

153153
for priority in EventPriority.inOrder {
@@ -173,7 +173,7 @@ open class EventHandler: ObservableThread, EventHandling {
173173
- Version: 1.0.0
174174
*/
175175
public override func main() {
176-
while isExecuting {
176+
while isExecuting && !isCancelled {
177177
eventsPending.wait() // This will make the Thread effectively "sleep" until there are Events pending
178178
processAllEvents() // Once there's at least one Event waiting, we will Process it/them.
179179
}

Sources/EventDrivenSwift/EventListener/EventListenable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Foundation
1313
- Author: Simon J. Stuart
1414
- Version: 3.0.0
1515
*/
16-
typealias EventCallback = (_ event: any Eventable, _ priority: EventPriority) -> ()
16+
public typealias EventCallback = (_ event: any Eventable, _ priority: EventPriority) -> ()
1717

1818
/**
1919
Convienience `typealias` used for Typed Event Callbacks
@@ -61,7 +61,7 @@ public protocol EventListenable: AnyObject, EventReceiving {
6161
- executeOn: Tells the `EventListenable` whether to execute the Callback on the `requester`'s Thread, or the Listener's.
6262
- Returns: A `UUID` value representing the `token` associated with this Event Callback
6363
*/
64-
@discardableResult func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn) -> UUID
64+
@discardableResult func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn) -> EventListenerHandling
6565

6666
/**
6767
Locates and removes the given Listener `token` (if it exists)

Sources/EventDrivenSwift/EventListener/EventListener.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ open class EventListener: EventHandler, EventListenable {
8080
}
8181
}
8282

83-
@discardableResult public func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread) -> UUID {
83+
@discardableResult public func addListener<TEvent: Eventable>(_ requester: AnyObject?, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread) -> EventListenerHandling {
8484
let eventTypeName = String(reflecting: forEventType)
8585
let method: EventCallback = { event, priority in
8686
self.callTypedEventCallback(callback, forEvent: event, priority: priority)
@@ -95,7 +95,7 @@ open class EventListener: EventHandler, EventListenable {
9595

9696
/// We automatically register the Listener with the Central Event Dispatcher
9797
EventCentral.shared.addReceiver(self, forEventType: forEventType)
98-
return eventListenerContainer.token
98+
return EventListenerHandler(eventListenable: self, token: eventListenerContainer.token)
9999
}
100100

101101
public func removeListener(_ token: UUID) {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// EventListening.swift
3+
// Copyright (c) 2022, Flowduino
4+
// Authored by Simon J. Stuart on 4th August 2022
5+
//
6+
// Subject to terms, restrictions, and liability waiver of the MIT License
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
Apply `EventListening` to any `Class` intent on listening for `Eventable`s to register `@EventMethod`-decorated (immutable) Listeners via Reflection.
13+
- Author: Simon J. Stuart
14+
- Version: 4.1.0
15+
*/
16+
public protocol EventListening: AnyObject {
17+
/**
18+
Invoke this method to automatically register any Event Listener callback bearing the `@EventMethod` wrapper.
19+
- Author: Simon J. Stuart
20+
- Version: 4.1.0
21+
- Note: Any Event Callback implemented this way will be automatically registered for you.
22+
````
23+
@EventMethod<MyEventThreadType, MyEventType>
24+
private var onMyEvent = {
25+
(self, event: MyEventType, priority: EventPriority) in
26+
/// Do something with `MyEventType` via its `event` reference here
27+
}
28+
````
29+
*/
30+
func registerListeners()
31+
}
32+
33+
/**
34+
Universal implementations to automatically Register and Unregister `@EventMethod`-decorated Event Listener Callbacks using Reflection
35+
- Author: Simon J. Stuart
36+
- Version: 4.1.0
37+
*/
38+
public extension EventListening {
39+
func registerListeners() {
40+
let mirror = Mirror(reflecting: self)
41+
for child in mirror.children {
42+
if var child = child.value as? (any EventMethodContainer) {
43+
child.prepare(owner: self)
44+
}
45+
}
46+
}
47+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// EventListenerHandler.swift
3+
// Copyright (c) 2022, Flowduino
4+
// Authored by Simon J. Stuart on 28th August 2022
5+
//
6+
// Subject to terms, restrictions, and liability waiver of the MIT License
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
A Handler for an `EventListener` your code has registered. You can use this to revoke your Event Listeners at any time.
13+
- Author: Simon J. Stuart
14+
- Version: 4.1.0
15+
*/
16+
public class EventListenerHandler: EventListenerHandling {
17+
/**
18+
`weak` reference to the `EventListenable` against which this Listener is registered.
19+
- Author: Simon J. Stuart
20+
- Version: 4.1.0
21+
*/
22+
private weak var eventListenable: EventListenable?
23+
24+
/**
25+
This is the Token Key assoicated with your Listener
26+
- Author: Simon J. Stuart
27+
- Version: 4.1.0
28+
*/
29+
private var token: UUID
30+
31+
public func remove() {
32+
eventListenable?.removeListener(token)
33+
}
34+
35+
public init(eventListenable: EventListenable, token: UUID) {
36+
self.eventListenable = eventListenable
37+
self.token = token
38+
}
39+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// EventListenerHandling.swift
3+
// Copyright (c) 2022, Flowduino
4+
// Authored by Simon J. Stuart on 28th August 2022
5+
//
6+
// Subject to terms, restrictions, and liability waiver of the MIT License
7+
//
8+
9+
import Foundation
10+
11+
/**
12+
A Handler for an `EventListener` your code has registered. You can use this to revoke your Event Listeners at any time.
13+
- Author: Simon J. Stuart
14+
- Version: 4.1.0
15+
*/
16+
public protocol EventListenerHandling: AnyObject {
17+
/**
18+
Removes your Event Listener
19+
*/
20+
func remove()
21+
}

Sources/EventDrivenSwift/EventReceiver/EventReceiver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ import Observable
2020
- Note: `EventThread` inherits from this
2121
*/
2222
open class EventReceiver: EventHandler, EventReceiving {
23-
23+
2424
}

0 commit comments

Comments
 (0)