Skip to content

Commit 68d3876

Browse files
committed
Remove OptionalStoreView and add UIScheduler
1 parent 94ef253 commit 68d3876

File tree

8 files changed

+127
-141
lines changed

8 files changed

+127
-141
lines changed

Sources/RecombinePackage/Store/AnyStore.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Combine
22

3-
public class AnyStore<BaseState, SubState, RawAction, BaseRefinedAction, SubRefinedAction>: StoreProtocol {
3+
public class AnyStore<BaseState: Equatable, SubState: Equatable, RawAction, BaseRefinedAction, SubRefinedAction>: StoreProtocol {
44
public let underlying: BaseStore<BaseState, RawAction, BaseRefinedAction>
55
public let stateLens: (BaseState) -> SubState
66
public let actionPromotion: (SubRefinedAction) -> BaseRefinedAction

Sources/RecombinePackage/Store/BaseStore.swift

+11-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Combine
22

3-
public class BaseStore<State, RawAction, RefinedAction>: StoreProtocol {
3+
public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtocol {
44
public typealias SubState = State
55
public typealias SubRefinedAction = RefinedAction
66
public typealias Action = ActionStrata<RawAction, RefinedAction>
@@ -10,62 +10,42 @@ public class BaseStore<State, RawAction, RefinedAction>: StoreProtocol {
1010
public var underlying: BaseStore<State, RawAction, RefinedAction> { self }
1111
public let stateLens: (State) -> State = { $0 }
1212
public let rawActions = PassthroughSubject<RawAction, Never>()
13-
public let refinedActions = PassthroughSubject<RefinedAction, Never>()
13+
public let refinedActions = PassthroughSubject<[RefinedAction], Never>()
1414
public let actionPromotion: (RefinedAction) -> RefinedAction = { $0 }
1515
public var actions: AnyPublisher<Action, Never> {
1616
Publishers.Merge(
1717
rawActions.map(Action.raw),
18-
refinedActions.map(Action.refined)
18+
refinedActions.flatMap(\.publisher).map(Action.refined)
1919
)
2020
.eraseToAnyPublisher()
2121
}
22-
private let stateEquality: (State, State) -> Bool
2322
private var cancellables = Set<AnyCancellable>()
2423

25-
public required init<S: Scheduler, R: Reducer>(
24+
public init<S: Scheduler, R: Reducer>(
2625
state: State,
27-
stateEquality: @escaping (State, State) -> Bool,
2826
reducer: R,
29-
middleware: Middleware<State, RawAction, RefinedAction>,
27+
middleware: Middleware<State, RawAction, RefinedAction> = .init { _, _ in Empty() },
3028
publishOn scheduler: S
3129
) where R.State == State, R.Action == RefinedAction {
3230
self.state = state
33-
self.stateEquality = stateEquality
34-
31+
3532
rawActions.flatMap { [unowned self] action in
3633
middleware.transform($state.first(), action)
3734
}
35+
.map { [$0] }
3836
.subscribe(refinedActions)
3937
.store(in: &cancellables)
4038

41-
refinedActions.scan(state) { state, action in
42-
reducer.reduce(
43-
state: state,
44-
action: action
45-
)
39+
refinedActions.scan(state) { state, actions in
40+
actions.reduce(state, reducer.reduce)
4641
}
47-
.removeDuplicates(by: stateEquality)
42+
.removeDuplicates()
4843
.receive(on: scheduler)
4944
.sink { [unowned self] state in
5045
self.state = state
5146
}
5247
.store(in: &cancellables)
5348
}
54-
55-
public convenience init<S: Scheduler, R: Reducer>(
56-
state: State,
57-
reducer: R,
58-
middleware: Middleware<State, RawAction, RefinedAction>,
59-
publishOn scheduler: S
60-
) where R.State == State, R.Action == RefinedAction, State: Equatable {
61-
self.init(
62-
state: state,
63-
stateEquality: ==,
64-
reducer: reducer,
65-
middleware: middleware,
66-
publishOn: scheduler
67-
)
68-
}
6949

7050
public func lensing<NewState, NewAction>(
7151
state lens: @escaping (SubState) -> NewState,
@@ -81,7 +61,7 @@ public class BaseStore<State, RawAction, RefinedAction>: StoreProtocol {
8161
}
8262

8363
open func dispatch<S: Sequence>(refined actions: S) where S.Element == RefinedAction {
84-
actions.forEach(self.refinedActions.send)
64+
self.refinedActions.send(.init(actions))
8565
}
8666

8767
open func dispatch<S: Sequence>(raw actions: S) where S.Element == RawAction {

Sources/RecombinePackage/Store/LensedStore.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Combine
22

3-
public class LensedStore<BaseState, SubState: Equatable, RawAction, BaseRefinedAction, SubRefinedAction>: StoreProtocol {
3+
public class LensedStore<BaseState: Equatable, SubState: Equatable, RawAction, BaseRefinedAction, SubRefinedAction>: StoreProtocol {
44
public typealias StoreType = BaseStore<BaseState, RawAction, BaseRefinedAction>
55
@Published
66
public private(set) var state: SubState

Sources/RecombinePackage/Store/OptionalStoreView.swift

-47
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,22 @@
11
import Combine
22
import SwiftUI
33

4-
public class PreviewStore<State: Equatable, RawAction, RefinedAction>: StoreProtocol {
5-
public typealias SubState = State
6-
public typealias SubRefinedAction = RefinedAction
7-
@Published
8-
public private(set) var state: State
9-
public var statePublisher: Published<State>.Publisher { $state }
10-
public let underlying: BaseStore<State, RawAction, RefinedAction>
11-
public let stateLens: (State) -> State = { $0 }
12-
public let actionPromotion: (RefinedAction) -> RefinedAction = { $0 }
13-
14-
private var cancellables = Set<AnyCancellable>()
15-
16-
public required init(
4+
public class PreviewBaseStore<
5+
State: Equatable,
6+
RawAction,
7+
RefinedAction
8+
>: BaseStore<
9+
State,
10+
RawAction,
11+
RefinedAction
12+
> {
13+
public convenience init(
1714
state: State
1815
) {
19-
self.state = state
20-
self.underlying = BaseStore(
16+
self.init(
2117
state: state,
2218
reducer: PureReducer(),
23-
middleware: .init { _, _ in Empty() },
2419
publishOn: RunLoop.main
2520
)
2621
}
27-
28-
public func lensing<NewState, NewAction>(
29-
state lens: @escaping (SubState) -> NewState,
30-
actions transform: @escaping (NewAction) -> SubRefinedAction
31-
) -> LensedStore<
32-
State,
33-
NewState,
34-
RawAction,
35-
RefinedAction,
36-
NewAction
37-
> {
38-
.init(store: underlying, lensing: lens, actionPromotion: transform)
39-
}
40-
41-
public func dispatch<S: Sequence>(refined _: S) where S.Element == RefinedAction {}
42-
43-
public func dispatch<S: Sequence>(raw _: S) where S.Element == RawAction {}
4422
}

Sources/RecombinePackage/Store/StoreProtocol.swift

+14-23
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import Combine
22
import SwiftUI
33

44
public protocol StoreProtocol: ObservableObject, Subscriber {
5-
associatedtype BaseState
6-
associatedtype SubState
5+
associatedtype BaseState: Equatable
6+
associatedtype SubState: Equatable
77
associatedtype RawAction
88
associatedtype BaseRefinedAction
99
associatedtype SubRefinedAction
@@ -40,18 +40,6 @@ public extension StoreProtocol {
4040
lensing(state: lens, actions: { $0 })
4141
}
4242

43-
func lensing<NewState>(
44-
state keyPath: KeyPath<SubState, NewState>
45-
) -> LensedStore<
46-
BaseState,
47-
NewState,
48-
RawAction,
49-
BaseRefinedAction,
50-
SubRefinedAction
51-
> {
52-
lensing(state: { $0[keyPath: keyPath] })
53-
}
54-
5543
func lensing<NewAction>(
5644
actions transform: @escaping (NewAction) -> SubRefinedAction
5745
) -> LensedStore<
@@ -63,18 +51,21 @@ public extension StoreProtocol {
6351
> {
6452
lensing(state: { $0 }, actions: transform)
6553
}
66-
67-
func lensing<NewState, NewAction>(
68-
state keyPath: KeyPath<SubState, NewState>,
69-
actions transform: @escaping (NewAction) -> SubRefinedAction
54+
55+
/// Create a LensedStore that cannot be updated with actions.
56+
/// - Parameters:
57+
/// - lens: A lens to the state property.
58+
/// - Returns: A `LensedStore`, whose state is lensed by `keyPath` and whose actions are of type `Never`.
59+
func lensingReadOnly<NewState>(
60+
state lens: @escaping (SubState) -> NewState
7061
) -> LensedStore<
7162
BaseState,
7263
NewState,
7364
RawAction,
7465
BaseRefinedAction,
75-
NewAction
66+
Never
7667
> {
77-
lensing(state: { $0[keyPath: keyPath] }, actions: transform)
68+
lensing(state: lens, actions: { _ -> SubRefinedAction in })
7869
}
7970
}
8071

@@ -86,7 +77,7 @@ public extension StoreProtocol {
8677
/// - Returns: A `Binding` whose getter is the property and whose setter dispatches the refined action.
8778
func binding<Value>(
8879
state lens: @escaping (SubState) -> Value,
89-
actions transform: @escaping (Value) -> SubRefinedAction
80+
action transform: @escaping (Value) -> SubRefinedAction
9081
) -> Binding<Value> {
9182
.init(
9283
get: { lens(self.state) },
@@ -96,10 +87,10 @@ public extension StoreProtocol {
9687

9788
/// Create a SwiftUI Binding from the `SubState` of the store and a `SubRefinedAction`.
9889
/// - Parameters:
99-
/// - actions: The refined action which will be called when the value is changed.
90+
/// - action: The refined action which will be called when the value is changed.
10091
/// - Returns: A `Binding` whose getter is the state and whose setter dispatches the refined action.
10192
func binding(
102-
actions transform: @escaping (SubState) -> SubRefinedAction
93+
action transform: @escaping (SubState) -> SubRefinedAction
10394
) -> Binding<SubState> {
10495
.init(
10596
get: { self.state },
+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/// MIT License
2+
///
3+
/// Copyright (c) 2020 Point-Free, Inc.
4+
///
5+
/// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
/// of this software and associated documentation files (the "Software"), to deal
7+
/// in the Software without restriction, including without limitation the rights
8+
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
/// copies of the Software, and to permit persons to whom the Software is
10+
/// furnished to do so, subject to the following conditions:
11+
///
12+
/// The above copyright notice and this permission notice shall be included in all
13+
/// copies or substantial portions of the Software.
14+
///
15+
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
/// SOFTWARE.
22+
23+
import Combine
24+
import Dispatch
25+
26+
/// A scheduler that executes its work on the main queue as soon as possible.
27+
///
28+
/// This scheduler is inspired by the
29+
/// [equivalent](https://github.com/ReactiveCocoa/ReactiveSwift/blob/58d92aa01081301549c48a4049e215210f650d07/Sources/Scheduler.swift#L92)
30+
/// scheduler in the [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift) project.
31+
///
32+
/// If `UIScheduler.shared.schedule` is invoked from the main thread then the unit of work will be
33+
/// performed immediately. This is in contrast to `DispatchQueue.main.schedule`, which will incur
34+
/// a thread hop before executing since it uses `DispatchQueue.main.async` under the hood.
35+
///
36+
/// This scheduler can be useful for situations where you need work executed as quickly as
37+
/// possible on the main thread, and for which a thread hop would be problematic, such as when
38+
/// performing animations.
39+
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
40+
public struct UIScheduler: Scheduler {
41+
public typealias SchedulerOptions = Never
42+
public typealias SchedulerTimeType = DispatchQueue.SchedulerTimeType
43+
44+
/// The shared instance of the UI scheduler.
45+
///
46+
/// You cannot create instances of the UI scheduler yourself. Use only the shared instance.
47+
public static let shared = Self()
48+
49+
public var now: SchedulerTimeType { DispatchQueue.main.now }
50+
public var minimumTolerance: SchedulerTimeType.Stride { DispatchQueue.main.minimumTolerance }
51+
52+
public func schedule(options: SchedulerOptions? = nil, _ action: @escaping () -> Void) {
53+
if DispatchQueue.getSpecific(key: key) == value {
54+
action()
55+
} else {
56+
DispatchQueue.main.schedule(action)
57+
}
58+
}
59+
60+
public func schedule(
61+
after date: SchedulerTimeType,
62+
tolerance: SchedulerTimeType.Stride,
63+
options: SchedulerOptions? = nil,
64+
_ action: @escaping () -> Void
65+
) {
66+
DispatchQueue.main.schedule(after: date, tolerance: tolerance, options: nil, action)
67+
}
68+
69+
public func schedule(
70+
after date: SchedulerTimeType,
71+
interval: SchedulerTimeType.Stride,
72+
tolerance: SchedulerTimeType.Stride,
73+
options: SchedulerOptions? = nil,
74+
_ action: @escaping () -> Void
75+
) -> Cancellable {
76+
DispatchQueue.main.schedule(
77+
after: date, interval: interval, tolerance: tolerance, options: nil, action
78+
)
79+
}
80+
81+
private init() { _ = setSpecific }
82+
}
83+
84+
private let key = DispatchSpecificKey<UInt8>()
85+
private let value: UInt8 = 0
86+
private var setSpecific: () = { DispatchQueue.main.setSpecific(key: key, value: value) }()

0 commit comments

Comments
 (0)