Skip to content

Commit d30eb1b

Browse files
committed
Condense dispatchSerial and dispatch into a single function
1 parent 628028b commit d30eb1b

File tree

8 files changed

+336
-109
lines changed

8 files changed

+336
-109
lines changed
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// Copyright (c) 2020 Combine Community, and/or Shai Mishali
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
import Combine
22+
23+
/// The ownership of an object
24+
///
25+
/// - seealso: https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID52
26+
public enum ObjectOwnership {
27+
/// Keep a strong hold of the object, preventing ARC
28+
/// from disposing it until its released or has no references.
29+
case strong
30+
31+
/// Weakly owned. Does not keep a strong hold of the object,
32+
/// allowing ARC to dispose it even if its referenced.
33+
case weak
34+
35+
/// Unowned. Similar to weak, but implicitly unwrapped so may
36+
/// crash if the object is released beore being accessed.
37+
case unowned
38+
}
39+
40+
public extension Publisher {
41+
/// Assigns a publisher’s output to a property of an object.
42+
///
43+
/// - parameter keyPath: A key path that indicates the subject to send into.
44+
/// - parameter object: The object that contains the subject.
45+
/// The subscriber sends into the object’s subject every time
46+
/// it receives a new value.
47+
/// - parameter ownership: The retainment / ownership strategy for the object, defaults to `strong`.
48+
///
49+
/// - returns: An AnyCancellable instance. Call cancel() on this instance when you no longer want
50+
/// the publisher to automatically assign the property. Deinitializing this instance
51+
/// will also cancel automatic assignment.
52+
func forward<Root: AnyObject, S: Subject>(
53+
to keyPath: KeyPath<Root, S>,
54+
on object: Root,
55+
ownership: ObjectOwnership
56+
) -> AnyCancellable
57+
where S.Output == Output, S.Failure == Failure
58+
{
59+
switch ownership {
60+
case .strong:
61+
return sink {
62+
object[keyPath: keyPath].send(completion: $0)
63+
} receiveValue: {
64+
object[keyPath: keyPath].send($0)
65+
}
66+
case .weak:
67+
return sink { [weak object] in
68+
object?[keyPath: keyPath].send(completion: $0)
69+
} receiveValue: { [weak object] in
70+
object?[keyPath: keyPath].send($0)
71+
}
72+
case .unowned:
73+
return sink { [unowned object] in
74+
object[keyPath: keyPath].send(completion: $0)
75+
} receiveValue: {
76+
object[keyPath: keyPath].send($0)
77+
}
78+
}
79+
}
80+
81+
func forward<Root1: AnyObject, Root2: AnyObject, S1: Subject, S2: Subject>(
82+
to keyPath1: KeyPath<Root1, S1>, on object1: Root1,
83+
and keyPath2: KeyPath<Root2, S2>, on object2: Root2,
84+
ownership: ObjectOwnership
85+
) -> AnyCancellable
86+
where S1.Output == Output, S1.Failure == Failure,
87+
S2.Output == Output, S2.Failure == Failure
88+
{
89+
switch ownership {
90+
case .strong:
91+
return sink {
92+
object1[keyPath: keyPath1].send(completion: $0)
93+
object2[keyPath: keyPath2].send(completion: $0)
94+
} receiveValue: {
95+
object1[keyPath: keyPath1].send($0)
96+
object2[keyPath: keyPath2].send($0)
97+
}
98+
case .weak:
99+
return sink { [weak object1, weak object2] in
100+
object1?[keyPath: keyPath1].send(completion: $0)
101+
object2?[keyPath: keyPath2].send(completion: $0)
102+
} receiveValue: { [weak object1, weak object2] in
103+
object1?[keyPath: keyPath1].send($0)
104+
object2?[keyPath: keyPath2].send($0)
105+
}
106+
case .unowned:
107+
return sink { [unowned object1, unowned object2] in
108+
object1[keyPath: keyPath1].send(completion: $0)
109+
object2[keyPath: keyPath2].send(completion: $0)
110+
} receiveValue: { [unowned object1, unowned object2] in
111+
object1[keyPath: keyPath1].send($0)
112+
object2[keyPath: keyPath2].send($0)
113+
}
114+
}
115+
}
116+
117+
func forward<Root1: AnyObject, Root2: AnyObject, Root3: AnyObject, S1: Subject, S2: Subject, S3: Subject>(
118+
to keyPath1: KeyPath<Root1, S1>, on object1: Root1,
119+
and keyPath2: KeyPath<Root2, S2>, on object2: Root2,
120+
and keyPath3: KeyPath<Root3, S3>, on object3: Root3,
121+
ownership: ObjectOwnership
122+
) -> AnyCancellable
123+
where S1.Output == Output, S1.Failure == Failure,
124+
S2.Output == Output, S2.Failure == Failure,
125+
S3.Output == Output, S3.Failure == Failure
126+
{
127+
switch ownership {
128+
case .strong:
129+
return sink {
130+
object1[keyPath: keyPath1].send(completion: $0)
131+
object2[keyPath: keyPath2].send(completion: $0)
132+
object3[keyPath: keyPath3].send(completion: $0)
133+
} receiveValue: {
134+
object1[keyPath: keyPath1].send($0)
135+
object2[keyPath: keyPath2].send($0)
136+
object3[keyPath: keyPath3].send($0)
137+
}
138+
case .weak:
139+
return sink { [weak object1, weak object2, weak object3] in
140+
object1?[keyPath: keyPath1].send(completion: $0)
141+
object2?[keyPath: keyPath2].send(completion: $0)
142+
object3?[keyPath: keyPath3].send(completion: $0)
143+
} receiveValue: { [weak object1, weak object2, weak object3] in
144+
object1?[keyPath: keyPath1].send($0)
145+
object2?[keyPath: keyPath2].send($0)
146+
object3?[keyPath: keyPath3].send($0)
147+
}
148+
case .unowned:
149+
return sink { [unowned object1, unowned object2, unowned object3] in
150+
object1[keyPath: keyPath1].send(completion: $0)
151+
object2[keyPath: keyPath2].send(completion: $0)
152+
object3[keyPath: keyPath3].send(completion: $0)
153+
} receiveValue: { [unowned object1, unowned object2] in
154+
object1[keyPath: keyPath1].send($0)
155+
object2[keyPath: keyPath2].send($0)
156+
object3[keyPath: keyPath3].send($0)
157+
}
158+
}
159+
}
160+
}
161+
162+
public extension Publisher where Self.Failure == Never {
163+
/// Assigns a publisher’s output to a property of an object.
164+
///
165+
/// - parameter keyPath: A key path that indicates the property to assign.
166+
/// - parameter object: The object that contains the property.
167+
/// The subscriber assigns the object’s property every time
168+
/// it receives a new value.
169+
/// - parameter ownership: The retainment / ownership strategy for the object, defaults to `strong`.
170+
///
171+
/// - returns: An AnyCancellable instance. Call cancel() on this instance when you no longer want
172+
/// the publisher to automatically assign the property. Deinitializing this instance
173+
/// will also cancel automatic assignment.
174+
func assign<Root: AnyObject>(
175+
to keyPath: ReferenceWritableKeyPath<Root, Self.Output>,
176+
on object: Root,
177+
ownership: ObjectOwnership
178+
) -> AnyCancellable {
179+
switch ownership {
180+
case .strong:
181+
return assign(to: keyPath, on: object)
182+
case .weak:
183+
return sink { [weak object] value in
184+
object?[keyPath: keyPath] = value
185+
}
186+
case .unowned:
187+
return sink { [unowned object] value in
188+
object[keyPath: keyPath] = value
189+
}
190+
}
191+
}
192+
193+
/// Assigns each element from a Publisher to properties of the provided objects
194+
///
195+
/// - Parameters:
196+
/// - keyPath1: The key path of the first property to assign.
197+
/// - object1: The first object on which to assign the value.
198+
/// - keyPath2: The key path of the second property to assign.
199+
/// - object2: The second object on which to assign the value.
200+
/// - ownership: The retainment / ownership strategy for the object, defaults to `strong`.
201+
///
202+
/// - Returns: A cancellable instance; used when you end assignment of the received value.
203+
/// Deallocation of the result will tear down the subscription stream.
204+
func assign<Root1: AnyObject, Root2: AnyObject>(
205+
to keyPath1: ReferenceWritableKeyPath<Root1, Output>, on object1: Root1,
206+
and keyPath2: ReferenceWritableKeyPath<Root2, Output>, on object2: Root2,
207+
ownership: ObjectOwnership
208+
) -> AnyCancellable {
209+
switch ownership {
210+
case .strong:
211+
return sink { value in
212+
object1[keyPath: keyPath1] = value
213+
object2[keyPath: keyPath2] = value
214+
}
215+
case .weak:
216+
return sink { [weak object1, weak object2] value in
217+
object1?[keyPath: keyPath1] = value
218+
object2?[keyPath: keyPath2] = value
219+
}
220+
case .unowned:
221+
return sink { [unowned object1, unowned object2] value in
222+
object1[keyPath: keyPath1] = value
223+
object2[keyPath: keyPath2] = value
224+
}
225+
}
226+
}
227+
228+
/// Assigns each element from a Publisher to properties of the provided objects
229+
///
230+
/// - Parameters:
231+
/// - keyPath1: The key path of the first property to assign.
232+
/// - object1: The first object on which to assign the value.
233+
/// - keyPath2: The key path of the second property to assign.
234+
/// - object2: The second object on which to assign the value.
235+
/// - keyPath3: The key path of the third property to assign.
236+
/// - object3: The third object on which to assign the value.
237+
/// - ownership: The retainment / ownership strategy for the object, defaults to `strong`.
238+
///
239+
/// - Returns: A cancellable instance; used when you end assignment of the received value.
240+
/// Deallocation of the result will tear down the subscription stream.
241+
func assign<Root1: AnyObject, Root2: AnyObject, Root3: AnyObject>(
242+
to keyPath1: ReferenceWritableKeyPath<Root1, Output>, on object1: Root1,
243+
and keyPath2: ReferenceWritableKeyPath<Root2, Output>, on object2: Root2,
244+
and keyPath3: ReferenceWritableKeyPath<Root3, Output>, on object3: Root3,
245+
ownership: ObjectOwnership
246+
) -> AnyCancellable {
247+
switch ownership {
248+
case .strong:
249+
return sink { value in
250+
object1[keyPath: keyPath1] = value
251+
object2[keyPath: keyPath2] = value
252+
object3[keyPath: keyPath3] = value
253+
}
254+
case .weak:
255+
return sink { [weak object1, weak object2, weak object3] value in
256+
object1?[keyPath: keyPath1] = value
257+
object2?[keyPath: keyPath2] = value
258+
object3?[keyPath: keyPath3] = value
259+
}
260+
case .unowned:
261+
return sink { [unowned object1, unowned object2, unowned object3] value in
262+
object1[keyPath: keyPath1] = value
263+
object2[keyPath: keyPath2] = value
264+
object3[keyPath: keyPath3] = value
265+
}
266+
}
267+
}
268+
}

Sources/RecombinePackage/Store/BaseStore.swift

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
3939
.filter { $0.count == 2 }
4040
.map { ($0[0], $0[1]) }
4141
)
42-
.sink(receiveValue: _actionsPairedWithState.send)
42+
.forward(
43+
to: \._actionsPairedWithState,
44+
on: self,
45+
ownership: .weak
46+
)
4347
.store(in: &cancellables)
4448

4549
_preMiddlewareRefinedActions
@@ -55,9 +59,11 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
5559
}
5660
}
5761
}
58-
.sink(receiveValue: { [weak self] in
59-
self?._postMiddlewareRefinedActions.send($0)
60-
})
62+
.forward(
63+
to: \._postMiddlewareRefinedActions,
64+
on: self,
65+
ownership: .weak
66+
)
6167
.store(in: &cancellables)
6268

6369
var group = Optional(DispatchGroup())
@@ -115,39 +121,21 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
115121
_actionsPairedWithState.eraseToAnyPublisher()
116122
}
117123

118-
open func dispatch<S: Sequence>(actions: S) where S.Element == Action {
119-
actions.forEach {
120-
switch $0 {
121-
case let .raw(actions):
122-
_rawActions.send(actions)
123-
actions.publisher
124-
.flatMap { [weak self] action in
125-
self.publisher().flatMap {
126-
$0.thunk.transform($0.$state.first(), action)
127-
}
128-
}
129-
.sink { [weak self] actions in
130-
self?.dispatch(actions: actions)
131-
}
132-
.store(in: &cancellables)
133-
case let .refined(actions):
134-
_preMiddlewareRefinedActions.send(actions)
135-
}
136-
}
137-
}
124+
open func dispatch<S: Sequence>(serially: Bool = false, actions: S) where S.Element == Action {
125+
let maxPublishers: Subscribers.Demand = serially ? .max(1) : .unlimited
126+
weak var `self` = self
138127

139-
open func dispatchSerially<S: Sequence>(actions: S) where S.Element == Action {
140128
func reduce(actions: Action) -> AnyPublisher<[RefinedAction], Never> {
141129
switch actions {
142130
case let .raw(actions):
143-
_rawActions.send(actions)
131+
self?._rawActions.send(actions)
144132
return actions.publisher
145-
.flatMap(maxPublishers: .max(1)) { [weak self] action in
133+
.flatMap(maxPublishers: maxPublishers) { [weak self] action in
146134
self.publisher().flatMap {
147135
$0.thunk.transform($0.$state.first(), action)
148136
}
149137
}
150-
.flatMap(maxPublishers: .max(1), reduce(actions:))
138+
.flatMap(maxPublishers: maxPublishers, reduce(actions:))
151139
.eraseToAnyPublisher()
152140
case let .refined(actions):
153141
return Just(actions).eraseToAnyPublisher()
@@ -156,8 +144,8 @@ public class BaseStore<State: Equatable, RawAction, RefinedAction>: StoreProtoco
156144

157145
actions
158146
.publisher
159-
.flatMap(maxPublishers: .max(1), reduce(actions:))
160-
.sink { [weak self] in
147+
.flatMap(maxPublishers: maxPublishers, reduce(actions:))
148+
.sink {
161149
self?._preMiddlewareRefinedActions.send($0)
162150
}
163151
.store(in: &cancellables)

0 commit comments

Comments
 (0)