#420 Invert subscription transforms in a reactive way (attempt No. 2)

Open Christian Tietze DivineDominion
Showing 12 of 20 files from the diff.

@@ -0,0 +1,47 @@
Loading
1 +
//
2 +
//  Disposable.swift
3 +
//  ReSwift
4 +
//
5 +
//  Created by Christian Tietze on 2019-08-03.
6 +
//  Copyright © 2019 ReSwift. All rights reserved.
7 +
//
8 +
9 +
internal protocol Disposable {
10 +
    /// Dispose resource callback.
11 +
    func dispose()
12 +
}
13 +
14 +
/// Create disposable that does nothing when cleaning up resources.
15 +
func createDisposable() -> Disposable {
16 +
    return NullDisposable.noOp
17 +
}
18 +
19 +
func createDisposable(with action: @escaping () -> Void) -> Disposable {
20 +
    return AnonymousDisposable(action: action)
21 +
}
22 +
23 +
private struct NullDisposable: Disposable {
24 +
    fileprivate static let noOp: Disposable = NullDisposable()
25 +
26 +
    init() {}
27 +
28 +
    func dispose() {
29 +
        // no-op
30 +
    }
31 +
}
32 +
33 +
private final class AnonymousDisposable: Disposable {
34 +
    public typealias DisposeAction = () -> Void
35 +
36 +
    private var disposeAction: DisposeAction?
37 +
38 +
    init(action: @escaping DisposeAction) {
39 +
        self.disposeAction = action
40 +
    }
41 +
42 +
    func dispose() {
43 +
        guard let action = self.disposeAction else { return }
44 +
        self.disposeAction = nil
45 +
        action()
46 +
    }
47 +
}

@@ -0,0 +1,57 @@
Loading
1 +
//
2 +
//  IncompleteSubscription.swift
3 +
//  ReSwift
4 +
//
5 +
//  Created by Christian Tietze on 2019-08-04.
6 +
//  Copyright © 2019 ReSwift. All rights reserved.
7 +
//
8 +
9 +
internal final class BlockSubscriber<S>: StoreSubscriber {
10 +
    typealias StoreSubscriberStateType = S
11 +
    private let block: (S) -> Void
12 +
13 +
    init(block: @escaping (S) -> Void) {
14 +
        self.block = block
15 +
    }
16 +
17 +
    func newState(state: S) {
18 +
        self.block(state)
19 +
    }
20 +
}
21 +
22 +
public final class IncompleteSubscription<RootStoreState: StateType, Substate> {
23 +
    typealias CompatibleStore = Store<RootStoreState>
24 +
25 +
    internal let store: CompatibleStore
26 +
    internal let observable: Observable<Substate>
27 +
28 +
    /// Used during transformations.
29 +
    internal init(store: CompatibleStore, observable: Observable<Substate>) {
30 +
        self.store = store
31 +
        self.observable = observable
32 +
    }
33 +
34 +
    func asObservable() -> Observable<Substate> {
35 +
        return observable
36 +
    }
37 +
}
38 +
39 +
extension IncompleteSubscription {
40 +
    @discardableResult
41 +
    public func subscribe<Subscriber: StoreSubscriber>(_ subscriber: Subscriber)
42 +
        -> SubscriptionToken
43 +
        where Subscriber.StoreSubscriberStateType == Substate
44 +
    {
45 +
        return self.store.subscribe(subscription: self, subscriber: subscriber)
46 +
    }
47 +
}
48 +
49 +
extension IncompleteSubscription where Substate: Equatable {
50 +
    @discardableResult
51 +
    public func subscribe<Subscriber: StoreSubscriber>(_ subscriber: Subscriber)
52 +
        -> SubscriptionToken
53 +
        where Subscriber.StoreSubscriberStateType == Substate
54 +
    {
55 +
        return self.store.subscribe(subscription: self, subscriber: subscriber)
56 +
    }
57 +
}

@@ -0,0 +1,44 @@
Loading
1 +
//
2 +
//  Sink.swift
3 +
//  ReSwift
4 +
//
5 +
//  Created by Christian Tietze on 2019-08-03.
6 +
//  Copyright © 2019 ReSwift. All rights reserved.
7 +
//
8 +
9 +
/// A Sink represents a disposable connection to an observer. Think of it as a cancelable
10 +
/// wrapper that forwards events.
11 +
///
12 +
/// - `observer` receives events as long as `isDisposed == false`.
13 +
/// - `dispose` forwards resource cleanup commands to the `cancel` handler.
14 +
///
15 +
/// ## Subclassing Notes
16 +
///
17 +
/// Subclass `Sink` for operators to inherit the resource cleanup.
18 +
/// Call `forward(state:)` to pass on events to eventual observers.
19 +
///
20 +
/// You could also delegate to a `Sink` instance instead of subclassing, but
21 +
/// the convenience of inheriting `Disposable` would then be lost.
22 +
internal class Sink<Observer: ObserverType>: Disposable {
23 +
    internal let observer: Observer
24 +
    internal let cancel: Cancelable
25 +
26 +
    init(observer: Observer, cancel: Cancelable) {
27 +
        self.observer = observer
28 +
        self.cancel = cancel
29 +
    }
30 +
31 +
    final func forward(state: Observer.Substate) {
32 +
        guard !isDisposed else { return }
33 +
        self.observer.on(state)
34 +
    }
35 +
36 +
    // TODO: Use NSLock/atomic values for thread safety
37 +
    private var isDisposed: Bool = false
38 +
39 +
    func dispose() {
40 +
        guard !isDisposed else { return }
41 +
        self.isDisposed = true
42 +
        cancel.dispose()
43 +
    }
44 +
}

@@ -0,0 +1,106 @@
Loading
1 +
//
2 +
//  SkipRepeats.swift
3 +
//  ReSwift
4 +
//
5 +
//  Created by Christian Tietze on 2019-08-05.
6 +
//  Copyright © 2019 ReSwift. All rights reserved.
7 +
//
8 +
9 +
extension IncompleteSubscription where Substate: Equatable {
10 +
    public func skipRepeats() -> IncompleteSubscription<RootStoreState, Substate> {
11 +
        return IncompleteSubscription<RootStoreState, Substate>(
12 +
            store: self.store,
13 +
            observable: self.observable.skipRepeats())
14 +
    }
15 +
}
16 +
17 +
extension ObservableType where Substate: Equatable {
18 +
    func skipRepeats() -> Observable<Substate> {
19 +
        return self.skipRepeats({ $0 }, comparer: { ($0 == $1) })
20 +
    }
21 +
}
22 +
23 +
extension IncompleteSubscription {
24 +
    public func skipRepeats<Key: Equatable>(
25 +
        _ keySelector: @escaping (Substate) -> Key
26 +
        ) -> IncompleteSubscription<RootStoreState, Substate>
27 +
    {
28 +
        return self.skipRepeats(keySelector, comparer: { $0 == $1 })
29 +
    }
30 +
31 +
    public func skipRepeats(
32 +
        _ comparer: @escaping (Substate, Substate) -> Bool
33 +
        ) -> IncompleteSubscription<RootStoreState, Substate>
34 +
    {
35 +
        return self.skipRepeats({ $0 }, comparer: comparer)
36 +
    }
37 +
38 +
    public func skipRepeats<K>(
39 +
        _ keySelector: @escaping (Substate) -> K,
40 +
        comparer: @escaping (K, K) -> Bool
41 +
        ) -> IncompleteSubscription<RootStoreState, Substate>
42 +
    {
43 +
        return IncompleteSubscription<RootStoreState, Substate>(
44 +
            store: self.store,
45 +
            observable: self.observable.skipRepeats(keySelector, comparer: comparer))
46 +
    }
47 +
}
48 +
49 +
extension ObservableType {
50 +
    func skipRepeats<K>(_ keySelector: @escaping (Substate) -> K, comparer: @escaping (K, K) -> Bool)
51 +
        -> Observable<Substate> {
52 +
            return SkipRepeats(source: self.asObservable(), selector: keySelector, comparer: comparer)
53 +
    }
54 +
}
55 +
56 +
final private class SkipRepeats<Substate, Key>: Producer<Substate> {
57 +
    typealias KeySelector = (Substate) -> Key
58 +
    typealias EqualityComparer = (Key, Key) -> Bool
59 +
60 +
    private let source: Observable<Substate>
61 +
    fileprivate let selector: KeySelector
62 +
    fileprivate let comparer: EqualityComparer
63 +
64 +
    init(source: Observable<Substate>, selector: @escaping KeySelector, comparer: @escaping EqualityComparer) {
65 +
        self.source = source
66 +
        self.selector = selector
67 +
        self.comparer = comparer
68 +
    }
69 +
70 +
    override func run<Observer: ObserverType>(
71 +
        _ observer: Observer,
72 +
        cancel: Cancelable)
73 +
        -> (sink: Disposable, subscription: Disposable)
74 +
        where Observer.Substate == Substate
75 +
    {
76 +
        let sink = SkipRepeatsSink(parent: self, observer: observer, cancel: cancel)
77 +
        let subscription = self.source.subscribe(sink)
78 +
        return (sink: sink, subscription: subscription)
79 +
    }
80 +
}
81 +
82 +
final private class SkipRepeatsSink<Observer: ObserverType, Key>: Sink<Observer>, ObserverType {
83 +
    typealias Substate = Observer.Substate
84 +
85 +
    private let parent: SkipRepeats<Substate, Key>
86 +
    private var previousValue: Key?
87 +
88 +
    init(parent: SkipRepeats<Substate, Key>, observer: Observer, cancel: Cancelable) {
89 +
        self.parent = parent
90 +
        super.init(observer: observer, cancel: cancel)
91 +
    }
92 +
93 +
    func on(_ state: Substate) {
94 +
        let currentValue = self.parent.selector(state)
95 +
        var areEqual = false
96 +
97 +
        if let previousValue = self.previousValue {
98 +
            areEqual = self.parent.comparer(previousValue, currentValue)
99 +
        }
100 +
        self.previousValue = currentValue
101 +
102 +
        guard !areEqual else { return }
103 +
104 +
        self.forward(state: state)
105 +
    }
106 +
}

@@ -0,0 +1,30 @@
Loading
1 +
//
2 +
//  Observer.swift
3 +
//  ReSwift
4 +
//
5 +
//  Created by Christian Tietze on 2019-08-03.
6 +
//  Copyright © 2019 ReSwift. All rights reserved.
7 +
//
8 +
9 +
/// Receives a state update.
10 +
protocol ObserverType {
11 +
    associatedtype Substate
12 +
    func on(_ state: Substate)
13 +
}
14 +
15 +
/// Type-erased `ObserverType` that forwards events.
16 +
internal final class AnyObserver<Substate>: ObserverType {
17 +
    private let observer: (Substate) -> Void
18 +
19 +
    init(observer: @escaping (Substate) -> Void) {
20 +
        self.observer = observer
21 +
    }
22 +
23 +
    init<Observer: ObserverType>(_ observer: Observer) where Observer.Substate == Substate {
24 +
        self.observer = observer.on
25 +
    }
26 +
27 +
    func on(_ substate: Substate) {
28 +
        self.observer(substate)
29 +
    }
30 +
}

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Click to load this diff.
Loading diff...

Learn more Showing 11 files with coverage changes found.

New file ReSwift/CoreTypes/Inversion/IncompleteSubscription.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Sink.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Disposable.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Observer.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Observable.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Operators/Filter.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/SubscriptionToken.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Operators/Select.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Operators/SkipRepeats.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Observable+create.swift
New
Loading file...
New file ReSwift/CoreTypes/Inversion/Producer.swift
New
Loading file...
Files Coverage
ReSwift -12.67% 86.79%
Project Totals (16 files) 86.79%
Loading