1
//
2
//  Store.swift
3
//  ReSwift
4
//
5
//  Created by Benjamin Encz on 11/11/15.
6
//  Copyright © 2015 ReSwift Community. All rights reserved.
7
//
8

9
/**
10
 This class is the default implementation of the `StoreType` protocol. You will use this store in most
11
 of your applications. You shouldn't need to implement your own store.
12
 You initialize the store with a reducer and an initial application state. If your app has multiple
13
 reducers you can combine them by initializng a `MainReducer` with all of your reducers as an
14
 argument.
15
 */
16
open class Store<State>: StoreType {
17

18
    typealias SubscriptionType = SubscriptionBox<State>
19

20
    private(set) public var state: State! {
21 5
        didSet {
22 5
            subscriptions.forEach {
23 5
                if $0.subscriber == nil {
24 5
                    subscriptions.remove($0)
25 5
                } else {
26 5
                    $0.newValues(oldState: oldValue, newState: state)
27
                }
28
            }
29
        }
30
    }
31

32
    public lazy var dispatchFunction: DispatchFunction! = createDispatchFunction()
33

34
    private var reducer: Reducer<State>
35

36 4
    var subscriptions: Set<SubscriptionType> = []
37

38 4
    private var isDispatching = Synchronized<Bool>(false)
39

40
    /// Indicates if new subscriptions attempt to apply `skipRepeats` 
41
    /// by default.
42
    fileprivate let subscriptionsAutomaticallySkipRepeats: Bool
43

44
    public var middleware: [Middleware<State>] {
45 5
        didSet {
46 5
            dispatchFunction = createDispatchFunction()
47
        }
48
    }
49

50
    /// Initializes the store with a reducer, an initial state and a list of middleware.
51
    ///
52
    /// Middleware is applied in the order in which it is passed into this constructor.
53
    ///
54
    /// - parameter reducer: Main reducer that processes incoming actions.
55
    /// - parameter state: Initial state, if any. Can be `nil` and will be 
56
    ///   provided by the reducer in that case.
57
    /// - parameter middleware: Ordered list of action pre-processors, acting 
58
    ///   before the root reducer.
59
    /// - parameter automaticallySkipsRepeats: If `true`, the store will attempt 
60
    ///   to skip idempotent state updates when a subscriber's state type 
61
    ///   implements `Equatable`. Defaults to `true`.
62
    public required init(
63
        reducer: @escaping Reducer<State>,
64
        state: State?,
65
        middleware: [Middleware<State>] = [],
66
        automaticallySkipsRepeats: Bool = true
67 5
    ) {
68 5
        self.subscriptionsAutomaticallySkipRepeats = automaticallySkipsRepeats
69 5
        self.reducer = reducer
70 5
        self.middleware = middleware
71

72 5
        if let state = state {
73 5
            self.state = state
74 5
        } else {
75 5
            dispatch(ReSwiftInit())
76
        }
77
    }
78

79 5
    private func createDispatchFunction() -> DispatchFunction! {
80 5
        // Wrap the dispatch function with all middlewares
81 5
        return middleware
82 5
            .reversed()
83 5
            .reduce(
84 5
                { [unowned self] action in
85 5
                    self._defaultDispatch(action: action) },
86 5
                { dispatchFunction, middleware in
87 5
                    // If the store get's deinitialized before the middleware is complete; drop
88 5
                    // the action without dispatching.
89 5
                    let dispatch: (Action) -> Void = { [weak self] in self?.dispatch($0) }
90 5
                    let getState: () -> State? = { [weak self] in self?.state }
91 5
                    return middleware(dispatch, getState)(dispatchFunction)
92 5
            })
93
    }
94

95
    fileprivate func _subscribe<SelectedState, S: StoreSubscriber>(
96
        _ subscriber: S, originalSubscription: Subscription<State>,
97
        transformedSubscription: Subscription<SelectedState>?)
98
        where S.StoreSubscriberStateType == SelectedState
99
    {
100 5
        let subscriptionBox = self.subscriptionBox(
101 5
            originalSubscription: originalSubscription,
102 5
            transformedSubscription: transformedSubscription,
103 5
            subscriber: subscriber
104 5
        )
105

106 5
        subscriptions.update(with: subscriptionBox)
107

108 5
        if let state = self.state {
109 5
            originalSubscription.newValues(oldState: nil, newState: state)
110
        }
111
    }
112

113
    open func subscribe<S: StoreSubscriber>(_ subscriber: S)
114 5
        where S.StoreSubscriberStateType == State {
115 5
            subscribe(subscriber, transform: nil)
116
    }
117

118
    open func subscribe<SelectedState, S: StoreSubscriber>(
119
        _ subscriber: S, transform: ((Subscription<State>) -> Subscription<SelectedState>)?
120
    ) where S.StoreSubscriberStateType == SelectedState
121
    {
122 5
        // Create a subscription for the new subscriber.
123 5
        let originalSubscription = Subscription<State>()
124 5
        // Call the optional transformation closure. This allows callers to modify
125 5
        // the subscription, e.g. in order to subselect parts of the store's state.
126 5
        let transformedSubscription = transform?(originalSubscription)
127

128 5
        _subscribe(subscriber, originalSubscription: originalSubscription,
129 5
                   transformedSubscription: transformedSubscription)
130
    }
131

132
    func subscriptionBox<T>(
133
        originalSubscription: Subscription<State>,
134
        transformedSubscription: Subscription<T>?,
135
        subscriber: AnyStoreSubscriber
136 5
        ) -> SubscriptionBox<State> {
137

138 5
        return SubscriptionBox(
139 5
            originalSubscription: originalSubscription,
140 5
            transformedSubscription: transformedSubscription,
141 5
            subscriber: subscriber
142 5
        )
143
    }
144

145 5
    open func unsubscribe(_ subscriber: AnyStoreSubscriber) {
146 5
        #if swift(>=5.0)
147 5
        if let index = subscriptions.firstIndex(where: { return $0.subscriber === subscriber }) {
148 5
            subscriptions.remove(at: index)
149
        }
150 5
        #else
151 5
        if let index = subscriptions.index(where: { return $0.subscriber === subscriber }) {
152 5
            subscriptions.remove(at: index)
153
        }
154 5
        #endif
155
    }
156

157
    // swiftlint:disable:next identifier_name
158 5
    open func _defaultDispatch(action: Action) {
159 5
        guard !isDispatching.value else {
160 5
            raiseFatalError(
161 5
                "ReSwift:ConcurrentMutationError- Action has been dispatched while" +
162 5
                " a previous action is action is being processed. A reducer" +
163 5
                " is dispatching an action, or ReSwift is used in a concurrent context" +
164 5
                " (e.g. from multiple threads)."
165 5
            )
166
        }
167

168 5
        isDispatching.value { $0 = true }
169 5
        let newState = reducer(action, state)
170 5
        isDispatching.value { $0 = false }
171

172 5
        state = newState
173
    }
174

175 5
    open func dispatch(_ action: Action) {
176 5
        dispatchFunction(action)
177
    }
178

179
    @available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
180 5
    open func dispatch(_ actionCreatorProvider: @escaping ActionCreator) {
181 5
        if let action = actionCreatorProvider(state, self) {
182 5
            dispatch(action)
183
        }
184
    }
185

186
    @available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
187 5
    open func dispatch(_ asyncActionCreatorProvider: @escaping AsyncActionCreator) {
188 5
        dispatch(asyncActionCreatorProvider, callback: nil)
189
    }
190

191
    @available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
192
    open func dispatch(_ actionCreatorProvider: @escaping AsyncActionCreator,
193 5
                       callback: DispatchCallback?) {
194 5
        actionCreatorProvider(state, self) { actionProvider in
195 5
            let action = actionProvider(self.state, self)
196

197 5
            if let action = action {
198 5
                self.dispatch(action)
199 5
                callback?(self.state)
200
            }
201
        }
202
    }
203

204
    public typealias DispatchCallback = (State) -> Void
205

206
    @available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
207
    public typealias ActionCreator = (_ state: State, _ store: Store) -> Action?
208

209
    @available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
210
    public typealias AsyncActionCreator = (
211
        _ state: State,
212
        _ store: Store,
213
        _ actionCreatorCallback: @escaping ((ActionCreator) -> Void)
214
    ) -> Void
215
}
216

217
// MARK: Skip Repeats for Equatable States
218

219
extension Store {
220
    open func subscribe<SelectedState: Equatable, S: StoreSubscriber>(
221
        _ subscriber: S, transform: ((Subscription<State>) -> Subscription<SelectedState>)?
222
        ) where S.StoreSubscriberStateType == SelectedState
223
    {
224 4
        let originalSubscription = Subscription<State>()
225

226 4
        var transformedSubscription = transform?(originalSubscription)
227 4
        if subscriptionsAutomaticallySkipRepeats {
228 4
            transformedSubscription = transformedSubscription?.skipRepeats()
229
        }
230 4
        _subscribe(subscriber, originalSubscription: originalSubscription,
231 4
                   transformedSubscription: transformedSubscription)
232
    }
233
}
234

235
extension Store where State: Equatable {
236
    open func subscribe<S: StoreSubscriber>(_ subscriber: S)
237 5
        where S.StoreSubscriberStateType == State {
238 5
            guard subscriptionsAutomaticallySkipRepeats else {
239 5
                subscribe(subscriber, transform: nil)
240 5
                return
241
            }
242 5
            subscribe(subscriber, transform: { $0.skipRepeats() })
243
    }
244
}

Read our documentation on viewing source code .

Loading