Skip to content

Commit 84c9a7d

Browse files
committed
1.0.5
- Updated to use 1.1.0 of ThreadSafeSwift - Updated `ObservableThread` to use improvements from above - Updated `ObservableThreadSafeClass` to use improvements from above
1 parent 4c981de commit 84c9a7d

File tree

4 files changed

+115
-121
lines changed

4 files changed

+115
-121
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ let package = Package(
1919
],
2020
dependencies: [
2121
// Dependencies declare other packages that this package depends on.
22-
.package(url: "https://github.com/Flowduino/ThreadSafeSwift.git", from: "1.0.0"),
22+
.package(url: "https://github.com/Flowduino/ThreadSafeSwift.git", from: "1.1.0"),
2323
],
2424
targets: [
2525
// Targets are the basic building blocks of a package. A target can define a module or a test suite.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ let package = Package(
2727
dependencies: [
2828
.package(
2929
url: "https://github.com/Flowduino/Observable.git",
30-
.upToNextMajor(from: "1.0.4")
30+
.upToNextMajor(from: "1.0.5")
3131
),
3232
],
3333
//...
@@ -222,4 +222,4 @@ Any number of *Observer Protocols* can be marshalled by any of our *Observable*
222222

223223
## License
224224

225-
`ThreadSafeSwift` is available under the MIT license. See the [LICENSE file](./LICENSE) for more info.
225+
`Observable` is available under the MIT license. See the [LICENSE file](./LICENSE) for more info.

Sources/Observable/ObservableThread.swift

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ThreadSafeSwift
1212
/**
1313
Provides custom Observer subscription and notification behaviour for Threads
1414
- Author: Simon J. Stuart
15-
- Version: 1.0.4
15+
- Version: 1.0.5
1616
- Note: The Observers are behind a Semaphore Lock
1717
- Note: A "Revolving Door" solution has been implemented to ensure that Observer Callbacks can modify the Observers (add/remove) without causing a Deadlock.
1818
*/
@@ -44,92 +44,89 @@ open class ObservableThread: Thread, Observable, ObservableObject {
4444
/**
4545
Dictionary mapping an `ObjectIdentifer` (reference to an Observer Instance) against its `ObserverContainer`
4646
- Author: Simon J. Stuart
47-
- Version: 1.0.4
47+
- Version: 1.0.5
4848
*/
49-
private var observers = [ObjectIdentifier : ObserverContainer]()
50-
private var observerLock = DispatchSemaphore(value: 1)
49+
@ThreadSafeSemaphore private var observers = [ObjectIdentifier : ObserverContainer]()
5150

5251
/**
5352
Dictionary mapping an `ObjectIdentifer` (reference to an Observer Instance) against its `ObserverContainer`
5453
- Author: Simon J. Stuart
55-
- Version: 1.0.4
54+
- Version: 1.0.5
5655
- Note: This is used as a temporary "Holding Queue" when the `observers` Dictionary has its Lock retained by another Thread.
5756
*/
58-
private var observersAddQueue = [ObjectIdentifier : ObserverContainer]()
59-
private var observerAddLock = DispatchSemaphore(value: 1)
57+
@ThreadSafeSemaphore private var observersAddQueue = [ObjectIdentifier : ObserverContainer]()
6058

6159
/**
6260
Dictionary mapping an `ObjectIdentifer` (reference to an Observer Instance) against its `ObserverContainer`
6361
- Author: Simon J. Stuart
64-
- Version: 1.0.4
62+
- Version: 1.0.5
6563
- Note: This is used as a temporary "Holding Queue" when the `observers` Dictionary has its Lock retained by another Thread.
6664
*/
67-
private var observersRemoveQueue = [ObjectIdentifier : ObserverContainer]()
68-
private var observerRemoveLock = DispatchSemaphore(value: 1)
65+
@ThreadSafeSemaphore private var observersRemoveQueue = [ObjectIdentifier : ObserverContainer]()
6966

7067
public func addObserver<TObservationProtocol: AnyObject>(_ observer: TObservationProtocol) {
7168
let observation = ObserverContainer(observer: observer, dispatchQueue: OperationQueue.current?.underlyingQueue)
7269

73-
if observerLock.wait(timeout: DispatchTime.now().advanced(by: DispatchTimeInterval.milliseconds(10))) == .success {
74-
observers[ObjectIdentifier(observer)] = observation
75-
observerLock.signal()
76-
}
77-
else {
78-
observerAddLock.wait()
79-
observersAddQueue[ObjectIdentifier(observer)] = observation
80-
observerAddLock.signal()
70+
_observers.withTryLock { value in
71+
value[ObjectIdentifier(observer)] = observation
72+
} _: {
73+
self._observersAddQueue.withLock { value in
74+
value[ObjectIdentifier(observer)] = observation
75+
}
8176
}
8277
}
8378

8479
public func removeObserver<TObservationProtocol: AnyObject>(_ observer: TObservationProtocol) {
85-
let lockResult = observerLock.wait(timeout: DispatchTime.now().advanced(by: DispatchTimeInterval.milliseconds(10)))
86-
87-
if lockResult == .success { // If we can obtain the lock for the Main Observer Collection...
88-
observers.removeValue(forKey: ObjectIdentifier(observer)) // Simply remove it from the Collection
89-
observerLock.signal() // Release the Lock
90-
}
91-
else { // If we CAN'T get the Main Observer Collection's Lock...
92-
observerRemoveLock.wait() // Get the Remove Queue Lock
93-
observersRemoveQueue[ObjectIdentifier(observer)] = ObserverContainer(observer: observer, dispatchQueue: nil) // the Dispatch Queue doesn't matter here!
94-
observerRemoveLock.signal() // Release the Remove Queue Lock
80+
_observers.withTryLock { value in
81+
value.removeValue(forKey: ObjectIdentifier(observer))
82+
} _: {
83+
self._observersRemoveQueue.withLock { value in
84+
value[ObjectIdentifier(observer)] = ObserverContainer(observer: observer, dispatchQueue: nil) // the Dispatch Queue doesn't matter here!]
85+
}
9586
}
9687
}
9788

89+
/**
90+
Iterates all Observers and invokes your Closure if they conform to the expected Protocol
91+
- Author: Simon J. Stuart
92+
- Version: 1.0.5
93+
*/
9894
public func withObservers<TObservationProtocol>(_ code: @escaping (_ observer: TObservationProtocol) -> ()) {
99-
self.observerLock.wait()
100-
for (id, observation) in observers {
101-
guard let observer = observation.observer else { // Check if the Observer still exists
102-
observers.removeValue(forKey: id) // If it doesn't, remove the Observer from the collection...
103-
continue // ...then continue to the next one
95+
_observers.withLock { observers in
96+
for (id, observation) in observers {
97+
guard let observer = observation.observer else { // Check if the Observer still exists
98+
observers.removeValue(forKey: id) // If it doesn't, remove the Observer from the collection...
99+
continue // ...then continue to the next one
100+
}
101+
102+
if let typedObserver = observer as? TObservationProtocol {
103+
let dispatchQueue = observation.dispatchQueue ?? DispatchQueue.main
104+
dispatchQueue.async {
105+
code(typedObserver)
106+
}
107+
}
108+
}
109+
var addPending = [ObjectIdentifier : ObserverContainer]()
110+
var removePending = [ObjectIdentifier : ObserverContainer]()
111+
112+
// Add all pending from the Add Queue
113+
self._observersAddQueue.withLock { value in
114+
addPending = value
115+
value.removeAll()
116+
}
117+
for (key, value) in addPending {
118+
observers[key] = value
104119
}
105120

106-
if let typedObserver = observer as? TObservationProtocol {
107-
let dispatchQueue = observation.dispatchQueue ?? DispatchQueue.main
108-
dispatchQueue.async {
109-
code(typedObserver)
110-
}
121+
// Remove all pending from the Remove Queue
122+
self._observersRemoveQueue.withLock { value in
123+
removePending = value
124+
value.removeAll()
125+
}
126+
for (id) in removePending.keys {
127+
observers.removeValue(forKey: id)
111128
}
112129
}
113-
114-
self.observerAddLock.wait() // Lock the Add Queue
115-
let observersToAdd = self.observersAddQueue
116-
observersAddQueue.removeAll()
117-
self.observerAddLock.signal() // Release the Add Queue Lock
118-
119-
self.observerRemoveLock.wait() // Lock the Remove Queue
120-
let observersToRemove = self.observersRemoveQueue
121-
observersRemoveQueue.removeAll()
122-
self.observerRemoveLock.signal() // Release the Remove Queue Lock
123-
124-
// Add all of the Queued Observers
125-
for (id, observation) in observersToAdd {
126-
observers[id] = observation
127-
}
128-
// Remove all of the Queued Observers
129-
for (id) in observersToRemove.keys {
130-
observers.removeValue(forKey: id)
131-
}
132-
self.observerLock.signal()
133130
}
134131

135132
open func notifyChange() {

Sources/Observable/ObservableThreadSafeClass.swift

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ThreadSafeSwift
1212
/**
1313
Provides custom Observer subscription and notification behaviour for Classes that will be interacting with Multiple Threads
1414
- Author: Simon J. Stuart
15-
- Version: 1.0.4
15+
- Version: 1.0.5
1616
- Note: The Observers are behind a Semaphore Lock
1717
- Note: A "Revolving Door" solution has been implemented to ensure that Observer Callbacks can modify the Observers (add/remove) without causing a Deadlock.
1818
*/
@@ -44,92 +44,89 @@ open class ObservableThreadSafeClass: Observable, ObservableObject {
4444
/**
4545
Dictionary mapping an `ObjectIdentifer` (reference to an Observer Instance) against its `ObserverContainer`
4646
- Author: Simon J. Stuart
47-
- Version: 1.0.4
47+
- Version: 1.0.5
4848
*/
49-
private var observers = [ObjectIdentifier : ObserverContainer]()
50-
private var observerLock = DispatchSemaphore(value: 1)
49+
@ThreadSafeSemaphore private var observers = [ObjectIdentifier : ObserverContainer]()
5150

5251
/**
5352
Dictionary mapping an `ObjectIdentifer` (reference to an Observer Instance) against its `ObserverContainer`
5453
- Author: Simon J. Stuart
55-
- Version: 1.0.4
54+
- Version: 1.0.5
5655
- Note: This is used as a temporary "Holding Queue" when the `observers` Dictionary has its Lock retained by another Thread.
5756
*/
58-
private var observersAddQueue = [ObjectIdentifier : ObserverContainer]()
59-
private var observerAddLock = DispatchSemaphore(value: 1)
60-
57+
@ThreadSafeSemaphore private var observersAddQueue = [ObjectIdentifier : ObserverContainer]()
58+
6159
/**
6260
Dictionary mapping an `ObjectIdentifer` (reference to an Observer Instance) against its `ObserverContainer`
6361
- Author: Simon J. Stuart
64-
- Version: 1.0.4
62+
- Version: 1.0.5
6563
- Note: This is used as a temporary "Holding Queue" when the `observers` Dictionary has its Lock retained by another Thread.
6664
*/
67-
private var observersRemoveQueue = [ObjectIdentifier : ObserverContainer]()
68-
private var observerRemoveLock = DispatchSemaphore(value: 1)
69-
65+
@ThreadSafeSemaphore private var observersRemoveQueue = [ObjectIdentifier : ObserverContainer]()
66+
7067
public func addObserver<TObservationProtocol: AnyObject>(_ observer: TObservationProtocol) {
7168
let observation = ObserverContainer(observer: observer, dispatchQueue: OperationQueue.current?.underlyingQueue)
7269

73-
if observerLock.wait(timeout: DispatchTime.now().advanced(by: DispatchTimeInterval.milliseconds(10))) == .success {
74-
observers[ObjectIdentifier(observer)] = observation
75-
observerLock.signal()
76-
}
77-
else {
78-
observerAddLock.wait()
79-
observersAddQueue[ObjectIdentifier(observer)] = observation
80-
observerAddLock.signal()
70+
_observers.withTryLock { value in
71+
value[ObjectIdentifier(observer)] = observation
72+
} _: {
73+
self._observersAddQueue.withLock { value in
74+
value[ObjectIdentifier(observer)] = observation
75+
}
8176
}
8277
}
8378

8479
public func removeObserver<TObservationProtocol: AnyObject>(_ observer: TObservationProtocol) {
85-
let lockResult = observerLock.wait(timeout: DispatchTime.now().advanced(by: DispatchTimeInterval.milliseconds(10)))
86-
87-
if lockResult == .success { // If we can obtain the lock for the Main Observer Collection...
88-
observers.removeValue(forKey: ObjectIdentifier(observer)) // Simply remove it from the Collection
89-
observerLock.signal() // Release the Lock
90-
}
91-
else { // If we CAN'T get the Main Observer Collection's Lock...
92-
observerRemoveLock.wait() // Get the Remove Queue Lock
93-
observersRemoveQueue[ObjectIdentifier(observer)] = ObserverContainer(observer: observer, dispatchQueue: nil) // the Dispatch Queue doesn't matter here!
94-
observerRemoveLock.signal() // Release the Remove Queue Lock
80+
_observers.withTryLock { value in
81+
value.removeValue(forKey: ObjectIdentifier(observer))
82+
} _: {
83+
self._observersRemoveQueue.withLock { value in
84+
value[ObjectIdentifier(observer)] = ObserverContainer(observer: observer, dispatchQueue: nil) // the Dispatch Queue doesn't matter here!]
85+
}
9586
}
9687
}
9788

89+
/**
90+
Iterates all Observers and invokes your Closure if they conform to the expected Protocol
91+
- Author: Simon J. Stuart
92+
- Version: 1.0.5
93+
*/
9894
public func withObservers<TObservationProtocol>(_ code: @escaping (_ observer: TObservationProtocol) -> ()) {
99-
self.observerLock.wait()
100-
for (id, observation) in observers {
101-
guard let observer = observation.observer else { // Check if the Observer still exists
102-
observers.removeValue(forKey: id) // If it doesn't, remove the Observer from the collection...
103-
continue // ...then continue to the next one
95+
_observers.withLock { observers in
96+
for (id, observation) in observers {
97+
guard let observer = observation.observer else { // Check if the Observer still exists
98+
observers.removeValue(forKey: id) // If it doesn't, remove the Observer from the collection...
99+
continue // ...then continue to the next one
100+
}
101+
102+
if let typedObserver = observer as? TObservationProtocol {
103+
let dispatchQueue = observation.dispatchQueue ?? DispatchQueue.main
104+
dispatchQueue.async {
105+
code(typedObserver)
106+
}
107+
}
108+
}
109+
var addPending = [ObjectIdentifier : ObserverContainer]()
110+
var removePending = [ObjectIdentifier : ObserverContainer]()
111+
112+
// Add all pending from the Add Queue
113+
self._observersAddQueue.withLock { value in
114+
addPending = value
115+
value.removeAll()
116+
}
117+
for (key, value) in addPending {
118+
observers[key] = value
104119
}
105120

106-
if let typedObserver = observer as? TObservationProtocol {
107-
let dispatchQueue = observation.dispatchQueue ?? DispatchQueue.main
108-
dispatchQueue.async {
109-
code(typedObserver)
110-
}
121+
// Remove all pending from the Remove Queue
122+
self._observersRemoveQueue.withLock { value in
123+
removePending = value
124+
value.removeAll()
125+
}
126+
for (id) in removePending.keys {
127+
observers.removeValue(forKey: id)
111128
}
112129
}
113-
114-
self.observerAddLock.wait() // Lock the Add Queue
115-
let observersToAdd = self.observersAddQueue
116-
observersAddQueue.removeAll()
117-
self.observerAddLock.signal() // Release the Add Queue Lock
118-
119-
self.observerRemoveLock.wait() // Lock the Remove Queue
120-
let observersToRemove = self.observersRemoveQueue
121-
observersRemoveQueue.removeAll()
122-
self.observerRemoveLock.signal() // Release the Remove Queue Lock
123-
124-
// Add all of the Queued Observers
125-
for (id, observation) in observersToAdd {
126-
observers[id] = observation
127-
}
128-
// Remove all of the Queued Observers
129-
for (id) in observersToRemove.keys {
130-
observers.removeValue(forKey: id)
131-
}
132-
self.observerLock.signal()
133130
}
134131

135132
open func notifyChange() {

0 commit comments

Comments
 (0)