Skip to content

Commit e089c37

Browse files
committed
Close to v1.0.0 release-ready
All of the fundamentals are implemented, tested, and working. - Some Thread-Safety improvements will be performed on `queue` and `stack` and `listener` collections similar to those made in the Observable library
1 parent fbec556 commit e089c37

16 files changed

+1155
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,6 @@ fastlane/test_output
8888
# https://github.com/johnno1962/injectionforxcode
8989

9090
iOSInjectionProject/
91+
92+
# MacOS FileSystem
93+
*.DS_Store

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.resolved

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"pins" : [
3+
{
4+
"identity" : "observable",
5+
"kind" : "remoteSourceControl",
6+
"location" : "https://github.com/Flowduino/Observable.git",
7+
"state" : {
8+
"revision" : "40f11bea890bf21428d3afd091aaa3347c167f1c",
9+
"version" : "1.1.0"
10+
}
11+
},
12+
{
13+
"identity" : "threadsafeswift",
14+
"kind" : "remoteSourceControl",
15+
"location" : "https://github.com/Flowduino/ThreadSafeSwift.git",
16+
"state" : {
17+
"revision" : "1a25ee0eea2d74922f34da271347076772d42fc1",
18+
"version" : "1.1.0"
19+
}
20+
}
21+
],
22+
"version" : 2
23+
}

Package.swift

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// swift-tools-version: 5.7
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "EventDrivenSwift",
8+
platforms: [
9+
.macOS(.v10_15),
10+
.iOS(.v13),
11+
.tvOS(.v13),
12+
.watchOS(.v6)
13+
],
14+
products: [
15+
// Products define the executables and libraries a package produces, and make them visible to other packages.
16+
.library(
17+
name: "EventDrivenSwift",
18+
targets: ["EventDrivenSwift"]),
19+
],
20+
dependencies: [
21+
// Dependencies declare other packages that this package depends on.
22+
.package(url: "https://github.com/Flowduino/ThreadSafeSwift.git", .upToNextMajor(from: "1.1.0")),
23+
.package(url: "https://github.com/Flowduino/Observable.git", .upToNextMajor(from: "1.1.0")),
24+
],
25+
targets: [
26+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
27+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
28+
.target(
29+
name: "EventDrivenSwift",
30+
dependencies: [
31+
.product(name: "ThreadSafeSwift", package: "ThreadSafeSwift"),
32+
.product(name: "Observable", package: "Observable"),
33+
]
34+
),
35+
.testTarget(
36+
name: "EventDrivenSwiftTests",
37+
dependencies: [
38+
"EventDrivenSwift",
39+
.product(name: "ThreadSafeSwift", package: "ThreadSafeSwift"),
40+
.product(name: "Observable", package: "Observable"),
41+
]),
42+
]
43+
)

README.md

+363
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// EventCentral.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+
Singleton for the Central Event Handler.
13+
- Author: Simon J. Stuart
14+
- Version: 1.0.0
15+
to access the Central Event Handler.
16+
- Note: This is a Singleton!
17+
- Note: This is used when invoking the `queue` and `stack` methods of `Eventable`.
18+
*/
19+
final public class EventCentral: EventDispatcher {
20+
private static var _shared: EventDispatcher = EventCentral()
21+
22+
public static var shared: EventDispatchable {
23+
get {
24+
return _shared
25+
}
26+
}
27+
28+
override private init() {}
29+
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// EventDispatchMethod.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+
Specifies the Dispatch Method of a given Event
13+
- Author: Simon J. Stuart
14+
- Version: 1.0.0
15+
*/
16+
public enum EventDispatchMethod: CaseIterable {
17+
/// The Event was dispatched through the Queue
18+
case queue
19+
/// The Event was dispatched through the Stack
20+
case stack
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// EventPriority.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+
Specifies the Priority of a given Event
13+
- Author: Simon J. Stuart
14+
- Version: 1.0.0
15+
*/
16+
public enum EventPriority: CaseIterable {
17+
case lowest
18+
case low
19+
case normal
20+
case high
21+
case highest
22+
23+
/**
24+
Returns all of the `EventPriority` cases in order of `highest` to `lowest` (reverse order).
25+
- Author: Simon J. Stuart
26+
- Version: 1.0.0
27+
- Note: This is necessary because we always process Events in priority-order from `highest` to `lowest`
28+
*/
29+
public static let inOrder = Self.allCases.reversed()
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// Eventable.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+
Protocol describing anything that can be dispatched as an Event
13+
- Author: Simon J. Stuart
14+
- Version: 1.0.0
15+
- Note: You should **never** pass any `class` instance references along in an `Eventable` type.
16+
- Note: `Eventable` types **must** always be **immutable**
17+
*/
18+
public protocol Eventable {
19+
/**
20+
Dispatch the Event to the Central Queue with the given `priority`
21+
- Author: Simon J. Stuart
22+
- Version: 1.0.0
23+
- Parameters:
24+
- priority: The Priority with which to process this Event
25+
*/
26+
func queue(priority: EventPriority)
27+
28+
/**
29+
Dispatch the Event to the Central Stack with the given `priority`
30+
- Author: Simon J. Stuart
31+
- Version: 1.0.0
32+
- Parameters:
33+
- priority: The Priority with which to process this Event
34+
*/
35+
func stack(priority: EventPriority)
36+
}
37+
38+
/**
39+
Extension to provide Operator Overrides for `Eventable` types
40+
- Author: Simon J. Stuart
41+
- Version: 1.0.0
42+
*/
43+
extension Eventable {
44+
static func == (lhs: Self, rhs: Self) -> Bool {
45+
return String(reflecting: lhs) == String(reflecting: rhs)
46+
}
47+
}
48+
49+
/**
50+
Extension to provide transparent `queue` and `stack` dispatch via `EventCentral`
51+
- Author: Simon J. Stuart
52+
- Version: 1.0.0
53+
*/
54+
extension Eventable {
55+
public func queue(priority: EventPriority = .normal) {
56+
EventCentral.shared.queueEvent(self, priority: priority)
57+
}
58+
59+
public func stack(priority: EventPriority = .normal) {
60+
EventCentral.shared.stackEvent(self, priority: priority)
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// EventDispatchable.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+
Protocol describing anything that Dispatches Events
13+
- Author: Simon J. Stuart
14+
- Version: 1.0.0
15+
*/
16+
public protocol EventDispatchable: EventHandlable {
17+
/**
18+
Registers the given `listener` for the given `Eventable` Type
19+
- Author: Simon J. Stuart
20+
- Version: 1.0.0
21+
*/
22+
func addListener(_ listener: any EventReceivable, forEventType: Eventable.Type)
23+
24+
/**
25+
Unregisters the given `listener` from the given `Eventable` Type
26+
- Author: Simon J. Stuart
27+
- Version: 1.0.0
28+
*/
29+
func removeListener(_ listener: any EventReceivable, forEventType: Eventable.Type)
30+
31+
/**
32+
Unregisters the given `listener` from all `Eventable` Types
33+
- Author: Simon J. Stuart
34+
- Version: 1.0.0
35+
*/
36+
func removeListener(_ listener: any EventReceivable)
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// EventDispatcher.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+
import ThreadSafeSwift
11+
import Observable
12+
13+
public class EventDispatcher: EventHandler, EventDispatchable {
14+
struct ListenerContainer {
15+
weak var listener: (any EventReceivable)?
16+
}
17+
18+
/**
19+
Stores all of the Listeners against the fully-qualified name of the corresponding `Eventable` Type
20+
- Author: Simon J. Stuart
21+
- Version: 1.0.0
22+
*/
23+
@ThreadSafeSemaphore private var listeners = [String:[ListenerContainer]]() //TODO: Make this a Revolving Door collection!
24+
25+
public func addListener(_ listener: any EventReceivable, forEventType: Eventable.Type) {
26+
let eventTypeName = String(reflecting: forEventType)
27+
28+
_listeners.withLock { listeners in
29+
var bucket = listeners[eventTypeName]
30+
let newBucket = bucket == nil
31+
if newBucket { bucket = [ListenerContainer]() } /// If there's no Bucket for this Event Type, create one
32+
33+
/// If it's NOT a New Bucket, and the Bucket already contains this Listener...
34+
if !newBucket && bucket!.contains(where: { listenerContainer in
35+
listenerContainer.listener != nil && ObjectIdentifier(listenerContainer.listener!) == ObjectIdentifier(listener)
36+
}) {
37+
return // ... just Return!
38+
}
39+
40+
/// If we reach here, the Listener is not already in the Bucket, so let's add it!
41+
bucket!.append(ListenerContainer(listener: listener))
42+
listeners[eventTypeName] = bucket!
43+
}
44+
}
45+
46+
public func removeListener(_ listener: any EventReceivable, forEventType: Eventable.Type) {
47+
let eventTypeName = String(reflecting: forEventType)
48+
49+
_listeners.withLock { listeners in
50+
var bucket = listeners[eventTypeName]
51+
if bucket == nil { return } /// Can't remove a Listener if there isn't even a Bucket for hte Event Type
52+
53+
/// Remove any Listeners from this Event-Type Bucket for the given `listener` instance.
54+
bucket!.removeAll { listenerContainer in
55+
listenerContainer.listener != nil && ObjectIdentifier(listenerContainer.listener!) == ObjectIdentifier(listener)
56+
}
57+
}
58+
}
59+
60+
public func removeListener(_ listener: any EventReceivable) {
61+
_listeners.withLock { listeners in
62+
for (eventTypeName, bucket) in listeners { /// Iterate every Event Type
63+
var newBucket = bucket // Copy the Bucket
64+
newBucket.removeAll { listenerContainer in /// Remove any occurences of the given Listener from the Bucket
65+
listenerContainer.listener != nil && ObjectIdentifier(listenerContainer.listener!) == ObjectIdentifier(listener)
66+
}
67+
listeners[eventTypeName] = newBucket /// Update the Bucket for this Event Type
68+
}
69+
}
70+
}
71+
72+
/**
73+
Dispatch the Event to all Subscribed Listeners
74+
- Author: Simon J. Stuart
75+
- Version: 1.0.0
76+
*/
77+
override internal func processEvent(_ event: any Eventable, dispatchMethod: EventDispatchMethod, priority: EventPriority) {
78+
let eventTypeName = String(reflecting: type(of: event))
79+
80+
_listeners.withLock { listeners in
81+
let bucket = listeners[eventTypeName]
82+
if bucket == nil { return } /// No Listeners, so nothing more to do!
83+
84+
var newBucket: [ListenerContainer]? = nil
85+
86+
for listener in bucket! {
87+
if listener.listener == nil { /// If the Listener is `nil`...
88+
if newBucket != nil { continue } /// ...If we've already removed all `nil` values, move on
89+
///... otherwise, remove the Listener from this bucket
90+
newBucket = bucket
91+
newBucket?.removeAll(where: { listenerContainer in
92+
listenerContainer.listener == nil
93+
})
94+
continue
95+
}
96+
97+
// so, we have a listener... let's deal with it!
98+
switch dispatchMethod {
99+
case .stack:
100+
listener.listener!.stackEvent(event, priority: priority)
101+
case .queue:
102+
listener.listener!.queueEvent(event, priority: priority)
103+
}
104+
}
105+
if newBucket != nil { listeners[eventTypeName] = newBucket }
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)