Skip to content

Commit 1bf1498

Browse files
author
Kacper
authored
Signal upgrades and fixes
1 parent 694b367 commit 1bf1498

File tree

4 files changed

+169
-56
lines changed

4 files changed

+169
-56
lines changed

Futura/Futura/Signal/Emitter.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,11 @@ public final class Emitter<Value>: Signal<Value> {
6969
return self
7070
}
7171
}
72+
73+
extension Emitter where Value == Void {
74+
75+
/// Shortcut for emiting Void values
76+
public func emit() {
77+
self.emit(())
78+
}
79+
}

Futura/Futura/Signal/Signal.swift

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,26 @@
2121
/// passed before observation will never occour.
2222
public class Signal<Value> {
2323
internal typealias Token = Result<Value>
24-
24+
2525
private var subscriptionID: Subscription.ID = 0
26-
private let privateCollector: SubscriptionCollector = .init()
27-
26+
internal let internalCollector: SubscriptionCollector = .init()
27+
2828
internal let mtx: Mutex.Pointer = Mutex.make(recursive: true)
2929
internal var subscribers: [(id: Subscription.ID, subscriber: Subscriber<Value>)] = .init()
3030
internal weak var collector: SubscriptionCollector?
3131
internal var finish: Error??
3232
internal var isFinished: Bool {
33-
Mutex.lock(mtx)
34-
defer { Mutex.unlock(mtx) }
3533
if case .some = finish {
3634
return true
3735
} else {
3836
return false
3937
}
4038
}
41-
39+
4240
internal init(collector: SubscriptionCollector?) {
4341
self.collector = collector
4442
}
45-
43+
4644
internal func subscribe(_ body: @escaping (Event) -> Void) -> Subscription? {
4745
Mutex.lock(mtx)
4846
defer { Mutex.unlock(mtx) }
@@ -51,45 +49,50 @@ public class Signal<Value> {
5149
let subscriber: Subscriber<Value> = .init(body: body)
5250
let subscription: Subscription = .init(deactivation: { [weak subscriber] in
5351
subscriber?.deactivate()
54-
}, unsubscribtion: { [weak self] in
55-
guard let self = self else { return }
56-
Mutex.lock(self.mtx)
57-
defer { Mutex.unlock(self.mtx) }
58-
if let idx = self.subscribers.firstIndex(where: { $0.id == id }) {
59-
self.subscribers.remove(at: idx)
60-
} else { /* do nothing */ }
52+
}, unsubscribtion: { [weak self] in
53+
guard let self = self else { return }
54+
Mutex.lock(self.mtx)
55+
defer { Mutex.unlock(self.mtx) }
56+
if let idx = self.subscribers.firstIndex(where: { $0.id == id }) {
57+
self.subscribers.remove(at: idx)
58+
} else { /* do nothing */ }
6159
})
6260
subscribers.append((id: id, subscriber: subscriber))
6361
return subscription
6462
}
65-
63+
6664
internal func broadcast(_ token: Token) {
6765
Mutex.lock(mtx)
6866
defer { Mutex.unlock(mtx) }
69-
subscribers.forEach { $0.1.recieve(.token(token)) }
67+
for (_, subscriber) in self.subscribers {
68+
subscriber.recieve(.token(token))
69+
}
7070
}
71-
71+
7272
internal func finish(_ reason: Error? = nil) {
7373
Mutex.lock(mtx)
7474
defer { Mutex.unlock(mtx) }
75-
subscribers.forEach { $0.1.recieve(.finish(reason)) }
75+
guard !isFinished else { return }
7676
finish = .some(reason)
77+
for (_, subscriber) in subscribers {
78+
subscriber.recieve(.finish(reason))
79+
}
7780
let sub = subscribers
7881
// cache until end of scope to prevent deallocation of subscribers while making changes in subscribers dictionary - prevents crash
7982
subscribers = .init()
8083
}
81-
84+
8285
internal func collect(_ subscribtion: Subscription?) {
8386
Mutex.lock(mtx)
8487
defer { Mutex.unlock(mtx) }
8588
guard let subscribtion = subscribtion else { return }
8689
if let collector = collector {
8790
collector.collect(subscribtion)
8891
} else {
89-
privateCollector.collect(subscribtion)
92+
internalCollector.collect(subscribtion)
9093
}
9194
}
92-
95+
9396
deinit {
9497
finish()
9598
Mutex.destroy(mtx)
@@ -103,6 +106,14 @@ extension Signal {
103106
}
104107
}
105108

109+
extension Signal {
110+
111+
/// Returns new Signal instance than never emits
112+
public static var never: Signal<Value> {
113+
return Emitter<Value>().signal
114+
}
115+
}
116+
106117
public extension Signal {
107118
/// Handler used to observe values passed through this Signal instance.
108119
///
@@ -116,7 +127,7 @@ public extension Signal {
116127
})
117128
return self
118129
}
119-
130+
120131
/// Handler used to observe errors passed through this Signal instance.
121132
///
122133
/// - Parameter observer: Handler called every time Signal gets error.
@@ -129,9 +140,9 @@ public extension Signal {
129140
})
130141
return self
131142
}
132-
143+
133144
#warning("TODO: add tokens handler - either value or error without reference")
134-
145+
135146
/// Handler used to observe finishing of this Signal by ending (without error).
136147
/// It will be called immediately with given context if
137148
/// signal already ended.
@@ -156,7 +167,7 @@ public extension Signal {
156167
}
157168
return self
158169
}
159-
170+
160171
/// Handler used to observe finishing of this Signal by termination (with error).
161172
/// It will be called immediately with given context if
162173
/// signal already terminated.
@@ -181,7 +192,7 @@ public extension Signal {
181192
}
182193
return self
183194
}
184-
195+
185196
/// Handler used to observe finishing of this Signal either by ending or termination
186197
/// (with or without error). It will be called immediately with given context if
187198
/// signal already finished.

Futura/Futura/Signal/SignalScheduler.swift

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,52 @@ public extension Signal {
2929

3030
internal final class SignalScheduler<Value>: SignalForwarder<Value, Value> {
3131
private let associatedWorker: Worker
32-
32+
3333
internal init(source: Signal<Value>, worker: Worker) {
3434
self.associatedWorker = worker
3535
super.init(source: source, collector: source.collector)
3636
source.forward(to: self)
3737
}
38-
38+
3939
internal override func broadcast(_ token: Token) {
40-
Mutex.lock(mtx)
41-
defer { Mutex.unlock(mtx) }
42-
associatedWorker.schedule {
43-
self.subscribers.forEach { $0.1.recieve(.token(token)) }
40+
associatedWorker.schedule { [weak self] in
41+
guard let self = self else { return }
42+
Mutex.lock(self.mtx)
43+
defer { Mutex.unlock(self.mtx) }
44+
for (_, subscriber) in self.subscribers {
45+
subscriber.recieve(.token(token))
46+
}
4447
}
4548
}
46-
49+
4750
internal override func finish(_ reason: Error? = nil) {
4851
Mutex.lock(mtx)
4952
defer { Mutex.unlock(mtx) }
50-
associatedWorker.schedule {
51-
self.subscribers.forEach { $0.1.recieve(.finish(reason)) }
52-
self.finish = .some(reason)
53-
let sub = self.subscribers
54-
// cache until end of scope to prevent deallocation of subscribers while making changes in subscribers dictionary - prevents crash
55-
self.subscribers = .init()
53+
guard !self.isFinished else { return }
54+
let subscribersCache = subscribers
55+
let collectorCache = internalCollector
56+
// cache to ensure execution if signal was
57+
// deallocated in the mean time
58+
associatedWorker.schedule { [weak self] in
59+
if let self = self {
60+
Mutex.lock(self.mtx)
61+
defer { Mutex.unlock(self.mtx) }
62+
guard !self.isFinished else { return }
63+
self.finish = .some(reason)
64+
for (_, subscriber) in self.subscribers {
65+
subscriber.recieve(.finish(reason))
66+
}
67+
let sub = self.subscribers
68+
// cache until end of scope to prevent deallocation of subscribers while making changes in subscribers dictionary - prevents crash
69+
self.subscribers = .init()
70+
} else {
71+
for (_, subscriber) in subscribersCache {
72+
subscriber.recieve(.finish(reason))
73+
}
74+
collectorCache.deactivate()
75+
}
76+
5677
}
5778
}
79+
5880
}

Playground.playground/Contents.swift

Lines changed: 89 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -80,33 +80,104 @@ make(request: jsonRequest)
8080

8181
import UIKit
8282

83-
var buttonTapKey: Int = 0
84-
private class ClosureHolder<T> {
85-
let closure: (T?) -> Void
83+
public struct SignalSource<Subject> {
84+
internal let subject: Subject
85+
internal init(subject: Subject) {
86+
self.subject = subject
87+
}
88+
}
8689

87-
init(_ closure: @escaping (T?) -> Void) {
90+
internal class ClosureHolder<T> {
91+
private let closure: (T?) -> Void
92+
93+
internal init(_ closure: @escaping (T?) -> Void) {
8894
self.closure = closure
8995
}
90-
96+
9197
@objc
92-
func invoke(with any: Any) {
98+
internal func invoke(with any: Any) {
9399
closure(any as? T)
94100
}
95101
}
96102

97-
struct SignalSource<Subject> {
98-
let subject: Subject
99-
init(subject: Subject) {
100-
self.subject = subject
103+
internal class CleanupHolder {
104+
private let closure: () -> Void
105+
106+
internal init(_ closure: @escaping () -> Void) {
107+
self.closure = closure
108+
}
109+
110+
deinit {
111+
closure()
112+
}
113+
}
114+
115+
internal func swizzleInstance(method originalSelector: Selector, with swizzledSelector: Selector, for classType: AnyClass) {
116+
guard let originalMethod = class_getInstanceMethod(classType, originalSelector),
117+
let swizzledMethod = class_getInstanceMethod(classType, swizzledSelector) else {
118+
return
119+
}
120+
121+
let added = class_addMethod(classType,
122+
originalSelector,
123+
method_getImplementation(swizzledMethod),
124+
method_getTypeEncoding(swizzledMethod))
125+
if added {
126+
class_replaceMethod(classType,
127+
swizzledSelector,
128+
method_getImplementation(originalMethod),
129+
method_getTypeEncoding(originalMethod))
130+
} else {
131+
method_exchangeImplementations(originalMethod, swizzledMethod)
132+
}
133+
}
134+
public extension UIViewController {
135+
var signal: SignalSource<UIViewController> { return .init(subject: self) }
136+
}
137+
138+
fileprivate extension UIViewController {
139+
static var swizzle: Void = {
140+
swizzleInstance(method: #selector(UIViewController.viewWillAppear(_:)), with: #selector(UIViewController.swizzle_viewWillAppear(_:)), for: UIViewController.self)
141+
swizzleInstance(method: #selector(UIViewController.viewWillDisappear(_:)), with: #selector(UIViewController.swizzle_viewWillDisappear(_:)), for: UIViewController.self)
142+
}()
143+
144+
@objc func swizzle_viewWillAppear(_ animated: Bool) {
145+
self.swizzle_viewWillAppear(animated)
146+
self.signal.visibilityEmitter.emit(true)
147+
}
148+
149+
@objc func swizzle_viewWillDisappear(_ animated: Bool) {
150+
self.swizzle_viewWillDisappear(animated)
151+
self.signal.visibilityEmitter.emit(false)
101152
}
102153
}
103154

104-
extension UIButton {
155+
fileprivate var viewControllerVisibilityKey: Int = 0
156+
extension SignalSource where Subject: UIViewController {
157+
fileprivate var visibilityEmitter: Emitter<Bool> {
158+
if let signal = objc_getAssociatedObject(subject, &viewControllerVisibilityKey) as? Emitter<Bool> {
159+
return signal
160+
} else {
161+
_ = UIViewController.swizzle
162+
let emitter: Emitter<Bool> = .init()
163+
objc_setAssociatedObject(subject, &viewControllerVisibilityKey, emitter.signal, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
164+
return emitter
165+
}
166+
}
167+
168+
public var visibility: Signal<Bool> {
169+
return visibilityEmitter.signal
170+
}
171+
}
172+
173+
174+
public extension UIButton {
105175
var signal: SignalSource<UIButton> { return .init(subject: self) }
106176
}
107177

178+
fileprivate var buttonTapKey: Int = 0
108179
extension SignalSource where Subject: UIButton {
109-
var tap: Signal<Void> {
180+
public var tap: Signal<Void> {
110181
if let signal = objc_getAssociatedObject(subject, &buttonTapKey) as? Emitter<Void> {
111182
return signal
112183
} else {
@@ -122,18 +193,19 @@ extension SignalSource where Subject: UIButton {
122193
}
123194
}
124195

125-
extension UITextField {
196+
public extension UITextField {
126197
var signal: SignalSource<UITextField> { return .init(subject: self) }
127198
}
128199

200+
fileprivate var textFieldChangeKey: Int = 0
129201
extension SignalSource where Subject: UITextField {
130-
var text: Signal<String> {
131-
if let signal = objc_getAssociatedObject(subject, &buttonTapKey) as? Emitter<String> {
202+
public var text: Signal<String> {
203+
if let signal = objc_getAssociatedObject(subject, &textFieldChangeKey) as? Emitter<String> {
132204
return signal
133205
} else {
134206
let emitter: Emitter<String> = .init()
135-
objc_setAssociatedObject(subject, &buttonTapKey, emitter.signal, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
136-
207+
objc_setAssociatedObject(subject, &textFieldChangeKey, emitter.signal, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
208+
137209
let closureHolder = ClosureHolder<UITextField> {
138210
emitter.emit($0?.text ?? "")
139211
}

0 commit comments

Comments
 (0)