ReSwift / ReSwift
1
//
2
//  SubscriberWrapper.swift
3
//  ReSwift
4
//
5
//  Created by Virgilio Favero Neto on 4/02/2016.
6
//  Copyright © 2016 ReSwift Community. All rights reserved.
7
//
8

9
/// A box around subscriptions and subscribers.
10
///
11
/// Acts as a type-erasing wrapper around a subscription and its transformed subscription.
12
/// The transformed subscription has a type argument that matches the selected substate of the
13
/// subscriber; however that type cannot be exposed to the store.
14
///
15
/// The box subscribes either to the original subscription, or if available to the transformed
16
/// subscription and passes any values that come through this subscriptions to the subscriber.
17
class SubscriptionBox<State>: Hashable {
18

19
    private let originalSubscription: Subscription<State>
20
    weak var subscriber: AnyStoreSubscriber?
21
    private let objectIdentifier: ObjectIdentifier
22

23
    #if swift(>=5.0)
24 5
        func hash(into hasher: inout Hasher) {
25 5
            hasher.combine(self.objectIdentifier)
26
        }
27
    #elseif swift(>=4.2)
28
        #if compiler(>=5.0)
29
            func hash(into hasher: inout Hasher) {
30
                hasher.combine(self.objectIdentifier)
31
            }
32
        #else
33 1
            var hashValue: Int {
34 1
                return self.objectIdentifier.hashValue
35
            }
36
        #endif
37
    #else
38
        var hashValue: Int {
39
            return self.objectIdentifier.hashValue
40
        }
41
    #endif
42

43
    init<T>(
44
        originalSubscription: Subscription<State>,
45
        transformedSubscription: Subscription<T>?,
46
        subscriber: AnyStoreSubscriber
47 6
    ) {
48 6
        self.originalSubscription = originalSubscription
49 6
        self.subscriber = subscriber
50 6
        self.objectIdentifier = ObjectIdentifier(subscriber)
51

52 6
        // If we received a transformed subscription, we subscribe to that subscription
53 6
        // and forward all new values to the subscriber.
54 6
        if let transformedSubscription = transformedSubscription {
55 6
            transformedSubscription.observer = { [unowned self] _, newState in
56 6
                self.subscriber?._newState(state: newState as Any)
57
            }
58 6
        // If we haven't received a transformed subscription, we forward all values
59 6
        // from the original subscription.
60 6
        } else {
61 6
            originalSubscription.observer = { [unowned self] _, newState in
62 6
                self.subscriber?._newState(state: newState as Any)
63
            }
64
        }
65
    }
66

67 6
    func newValues(oldState: State, newState: State) {
68 6
        // We pass all new values through the original subscription, which accepts
69 6
        // values of type `<State>`. If present, transformed subscriptions will
70 6
        // receive this update and transform it before passing it on to the subscriber.
71 6
        self.originalSubscription.newValues(oldState: oldState, newState: newState)
72
    }
73

74 6
    static func == (left: SubscriptionBox<State>, right: SubscriptionBox<State>) -> Bool {
75 6
        return left.objectIdentifier == right.objectIdentifier
76
    }
77
}
78

79
/// Represents a subscription of a subscriber to the store. The subscription determines which new
80
/// values from the store are forwarded to the subscriber, and how they are transformed.
81
/// The subscription acts as a very-light weight signal/observable that you might know from
82
/// reactive programming libraries.
83
public class Subscription<State> {
84

85
    private func _select<Substate>(
86
        _ selector: @escaping (State) -> Substate
87
        ) -> Subscription<Substate>
88
    {
89 6
        return Subscription<Substate> { sink in
90 6
            self.observer = { oldState, newState in
91 6
                sink(oldState.map(selector) ?? nil, selector(newState))
92
            }
93
        }
94
    }
95

96
    // MARK: Public Interface
97

98
    /// Initializes a subscription with a sink closure. The closure provides a way to send
99
    /// new values over this subscription.
100 6
    public init(sink: @escaping (@escaping (State?, State) -> Void) -> Void) {
101 6
        // Provide the caller with a closure that will forward all values
102 6
        // to observers of this subscription.
103 6
        sink { old, new in
104 6
            self.newValues(oldState: old, newState: new)
105
        }
106
    }
107

108
    /// Provides a subscription that selects a substate of the state of the original subscription.
109
    /// - parameter selector: A closure that maps a state to a selected substate
110
    public func select<Substate>(
111
        _ selector: @escaping (State) -> Substate
112
        ) -> Subscription<Substate>
113
    {
114 6
        return self._select(selector)
115
    }
116

117
    /// Provides a subscription that selects a substate of the state of the original subscription.
118
    /// - parameter keyPath: A key path from a state to a substate
119
    public func select<Substate>(
120
        _ keyPath: KeyPath<State, Substate>
121
        ) -> Subscription<Substate>
122
    {
123 6
        return self._select { $0[keyPath: keyPath] }
124
    }
125

126
    /// Provides a subscription that skips certain state updates of the original subscription.
127
    /// - parameter isRepeat: A closure that determines whether a given state update is a repeat and
128
    /// thus should be skipped and not forwarded to subscribers.
129
    /// - parameter oldState: The store's old state, before the action is reduced.
130
    /// - parameter newState: The store's new state, after the action has been reduced.
131
    public func skipRepeats(_ isRepeat: @escaping (_ oldState: State, _ newState: State) -> Bool)
132 6
        -> Subscription<State> {
133 6
        return Subscription<State> { sink in
134 6
            self.observer = { oldState, newState in
135 6
                switch (oldState, newState) {
136 6
                case let (old?, new):
137 6
                    if !isRepeat(old, new) {
138 6
                        sink(oldState, newState)
139 6
                    } else {
140 6
                        return
141
                    }
142 6
                default:
143 6
                    sink(oldState, newState)
144
                }
145
            }
146
        }
147
    }
148

149
    /// The closure called with changes from the store.
150
    /// This closure can be written to for use in extensions to Subscription similar to `skipRepeats`
151
    public var observer: ((State?, State) -> Void)?
152

153
    // MARK: Internals
154

155 6
    init() {}
156

157
    /// Sends new values over this subscription. Observers will be notified of these new values.
158 6
    func newValues(oldState: State?, newState: State) {
159 6
        self.observer?(oldState, newState)
160
    }
161
}
162

163
extension Subscription where State: Equatable {
164 6
    public func skipRepeats() -> Subscription<State>{
165 6
        return self.skipRepeats(==)
166
    }
167
}
168

169
/// Subscription skipping convenience methods
170
extension Subscription {
171

172
    /// Provides a subscription that skips certain state updates of the original subscription.
173
    ///
174
    /// This is identical to `skipRepeats` and is provided simply for convenience.
175
    /// - parameter when: A closure that determines whether a given state update is a repeat and
176
    /// thus should be skipped and not forwarded to subscribers.
177
    /// - parameter oldState: The store's old state, before the action is reduced.
178
    /// - parameter newState: The store's new state, after the action has been reduced.
179 6
    public func skip(when: @escaping (_ oldState: State, _ newState: State) -> Bool) -> Subscription<State> {
180 6
        return self.skipRepeats(when)
181
    }
182

183
    /// Provides a subscription that only updates for certain state changes.
184
    ///
185
    /// This is effectively the inverse of `skip(when:)` / `skipRepeats(:)`
186
    /// - parameter when: A closure that determines whether a given state update should notify
187
    /// - parameter oldState: The store's old state, before the action is reduced.
188
    /// - parameter newState: The store's new state, after the action has been reduced.
189
    /// the subscriber.
190 6
    public func only(when: @escaping (_ oldState: State, _ newState: State) -> Bool) -> Subscription<State> {
191 6
        return self.skipRepeats { oldState, newState in
192 6
            return !when(oldState, newState)
193
        }
194
    }
195
}

Read our documentation on viewing source code .

Loading