Skip to content

Commit 71c2cad

Browse files
committed
EventListener enhancements
- `EventListener` can now invoke Callbacks on any of 3 Target Threads: - `.requesterThread` is the Thread belonging to the Callback itself - `.listenerThread` is the Thread belonging to the `EventListener` - `.taskThread` is an Ad-hoc `Task` Thread - README.MD updated for the above
1 parent e0486a1 commit 71c2cad

File tree

6 files changed

+59
-14
lines changed

6 files changed

+59
-14
lines changed

README.md

Lines changed: 15 additions & 2 deletions
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: "3.0.0")
128+
.upToNextMajor(from: "3.1.0")
129129
),
130130
],
131131
//...
@@ -355,7 +355,7 @@ An `EventListener` is a universal way of subscribing to *Events*, anywhere in yo
355355

356356
By 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.
357357

358-
**Important Note:** `EventListener` will always 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.
358+
**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.
359359

360360
Let'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.
361361

@@ -386,6 +386,19 @@ In the above example, whenever the *Reciprocal Event* named `TemperatureRatingEv
386386

387387
Don'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!
388388

389+
If you need your *Event Callback* to execute on the *Listener's* Thread, as of Version 3.1.0... you can!
390+
```swift
391+
listenerToken = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .listenerThread)
392+
```
393+
**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!
394+
**Important:** Executing the *Event Callback* on `.listnerThread` can potentially delay the invocation of other *Event Callbacks*. Only use this option when it is necessary.
395+
396+
You can also execute your *Event Callback* on an ad-hoc `Task`:
397+
```swift
398+
listenerToken = TemperatureRatingEvent.addListener(self, onTemperatureRatingEvent, executeOn: .taskThread)
399+
```
400+
**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!
401+
389402
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:
390403
```swift
391404
TemperatureRatingEvent.removeListener(listenerToken)

Sources/EventDrivenSwift/Central/EventCentral.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ 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) -> UUID where TEvent : Eventable {
78-
return _shared.eventListener.addListener(requester, callback, forEventType: forEventType)
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 {
78+
return _shared.eventListener.addListener(requester, callback, forEventType: forEventType, executeOn: executeOn)
7979
}
8080

8181
@inline(__always) public static func removeListener(_ token: UUID) {

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) -> UUID
71+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn) -> UUID
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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ 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>) -> UUID
46+
@discardableResult static func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn) -> UUID
4747

4848
/**
4949
Locates and removes the given Listener `token` (if it exists) from the Central Event Listener
@@ -80,8 +80,8 @@ extension Eventable {
8080
EventCentral.stackEvent(self, priority: priority)
8181
}
8282

83-
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>) -> UUID {
84-
return EventCentral.addListener(requester, callback, forEventType: Self.self)
83+
@discardableResult static public func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, executeOn: ExecuteEventOn = .requesterThread) -> UUID {
84+
return EventCentral.addListener(requester, callback, forEventType: Self.self, executeOn: executeOn)
8585
}
8686

8787
public static func removeListener(_ token: UUID) {

Sources/EventDrivenSwift/EventListener/EventListenable.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ typealias EventCallback = (_ event: any Eventable, _ priority: EventPriority) ->
2222
*/
2323
public typealias TypedEventCallback<TEvent: Any> = (_ event: TEvent, _ priority: EventPriority) -> ()
2424

25+
public enum ExecuteEventOn {
26+
/**
27+
Callback will execute on the context of the Requester's own Thread
28+
- Author: Simon J. Stuart
29+
- Version: 3.1.0
30+
*/
31+
case requesterThread
32+
/**
33+
Callback will execute on the context of the Listener's Thread
34+
- Author: Simon J. Stuart
35+
- Version: 3.1.0
36+
- Note: You must ensure your Callback (and all resources it uses) are Thread-Safe!
37+
*/
38+
case listenerThread
39+
/**
40+
Callback will execute on the context of an ad-hoc Task
41+
- Author: Simon J. Stuart
42+
- Version: 3.1.0
43+
- Note: You must ensure your Callback (and all resources it uses) are Thread-Safe!
44+
*/
45+
case taskThread
46+
}
2547
/**
2648
Provides a simple means of Receiving Events and invoking appropriate Callbacks
2749
- Author: Simon J. Stuart
@@ -36,9 +58,10 @@ public protocol EventListenable: AnyObject, EventReceivable {
3658
- requester: The Object owning the Callback Method
3759
- callback: The code to invoke for the given `Eventable` Type
3860
- forEventType: The `Eventable` Type for which to Register the Callback
61+
- executeOn: Tells the `EventListenable` whether to execute the Callback on the `requester`'s Thread, or the Listener's.
3962
- Returns: A `UUID` value representing the `token` associated with this Event Callback
4063
*/
41-
@discardableResult func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type) -> UUID
64+
@discardableResult func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn) -> UUID
4265

4366
/**
4467
Locates and removes the given Listener `token` (if it exists)

Sources/EventDrivenSwift/EventListener/EventListener.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ open class EventListener: EventHandler, EventListenable {
2727
weak var requester: AnyObject?
2828
var callback: EventCallback
2929
var dispatchQueue: DispatchQueue?
30+
var executeOn: ExecuteEventOn = .requesterThread
3031
}
3132

3233
/**
@@ -61,20 +62,28 @@ open class EventListener: EventHandler, EventListenable {
6162
removeListener(listener.token, typeOf: type(of: event)) // ... Unregister this Listener
6263
continue // Skip this one
6364
}
64-
// Execute the Callback on the Listener's own Dispatch Queue
65-
let dispatchQueue = listener.dispatchQueue ?? DispatchQueue.main
66-
dispatchQueue.async {
65+
switch listener.executeOn {
66+
case .requesterThread:
67+
let dispatchQueue = listener.dispatchQueue ?? DispatchQueue.main
68+
dispatchQueue.async {
69+
listener.callback(event, priority)
70+
}
71+
case .listenerThread:
6772
listener.callback(event, priority)
73+
case .taskThread:
74+
Task {
75+
listener.callback(event, priority)
76+
}
6877
}
6978
}
7079
}
7180

72-
@discardableResult public func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type) -> UUID {
81+
@discardableResult public func addListener<TEvent: Eventable>(_ requester: AnyObject, _ callback: @escaping TypedEventCallback<TEvent>, forEventType: Eventable.Type, executeOn: ExecuteEventOn = .requesterThread) -> UUID {
7382
let eventTypeName = String(reflecting: forEventType)
7483
let method: EventCallback = { event, priority in
7584
self.callTypedEventCallback(callback, forEvent: event, priority: priority)
7685
}
77-
let eventListenerContainer = EventListenerContainer(requester: requester, callback: method, dispatchQueue: OperationQueue.current?.underlyingQueue)
86+
let eventListenerContainer = EventListenerContainer(requester: requester, callback: method, dispatchQueue: OperationQueue.current?.underlyingQueue, executeOn: executeOn)
7887
_eventListeners.withLock { eventCallbacks in
7988
var bucket = eventCallbacks[eventTypeName]
8089
if bucket == nil { bucket = [EventListenerContainer]() } // Create a new bucket if there isn't already one!

0 commit comments

Comments
 (0)