Skip to content

Commit 01241c1

Browse files
authored
* Remove use of deferred where not required. (#4)
* Refactor Effect Cancellation logic * Remove unnecessary holding on to Disposables
1 parent 1668c76 commit 01241c1

File tree

12 files changed

+36
-126
lines changed

12 files changed

+36
-126
lines changed

Examples/CaseStudies/UIKitCaseStudies/CounterViewController.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ let counterReducer = Reducer<CounterState, CounterAction, CounterEnvironment> {
2727

2828
final class CounterViewController: UIViewController {
2929
let viewStore: ViewStore<CounterState, CounterAction>
30-
var cancellables: Set<AnyCancellable> = []
3130

3231
init(store: Store<CounterState, CounterAction>) {
3332
self.viewStore = ViewStore(store)

Examples/CaseStudies/UIKitCaseStudies/Internal/IfLetStoreController.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ final class IfLetStoreController<State, Action>: UIViewController {
77
let ifDestination: (Store<State, Action>) -> UIViewController
88
let elseDestination: () -> UIViewController
99

10-
private var cancellables: Set<AnyCancellable> = []
1110
private var viewController = UIViewController() {
1211
willSet {
1312
self.viewController.willMove(toParent: nil)

Examples/CaseStudies/UIKitCaseStudies/ListsOfState.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ let cellIdentifier = "Cell"
2525
final class CountersTableViewController: UITableViewController {
2626
let store: Store<CounterListState, CounterListAction>
2727
let viewStore: ViewStore<CounterListState, CounterListAction>
28-
var cancellables: Set<AnyCancellable> = []
2928

3029
var dataSource: [CounterState] = [] {
3130
didSet { self.tableView.reloadData() }

Examples/TicTacToe/Sources/Views-UIKit/AppViewController.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ struct UIKitAppView: UIViewControllerRepresentable {
2121

2222
class AppViewController: UINavigationController {
2323
let store: Store<AppState, AppAction>
24-
private var cancellables: Set<AnyCancellable> = []
2524

2625
init(store: Store<AppState, AppAction>) {
2726
self.store = store

Examples/TicTacToe/Sources/Views-UIKit/NewGameViewController.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ class NewGameViewController: UIViewController {
2222
let store: Store<NewGameState, NewGameAction>
2323
let viewStore: ViewStore<ViewState, ViewAction>
2424

25-
private var cancellables: Set<AnyCancellable> = []
26-
2725
init(store: Store<NewGameState, NewGameAction>) {
2826
self.store = store
2927
self.viewStore = ViewStore(store.scope(state: { $0.view }, action: NewGameAction.view))

Sources/ComposableArchitecture/Effect.swift

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extension Effect {
1717
/// An effect that does nothing and completes immediately. Useful for situations where you must
1818
/// return an effect, but you don't need to do anything.
1919
public static var none: Effect {
20-
return .empty
20+
.empty
2121
}
2222

2323
/// Creates an effect that executes some work in the real world that doesn't need to feed data
@@ -65,7 +65,7 @@ extension Effect {
6565
public static func deferred(_ createProducer: @escaping () -> SignalProducer<Value, Error>)
6666
-> SignalProducer<Value, Error>
6767
{
68-
return Effect<Void, Error>(value: ())
68+
Effect<Void, Error>(value: ())
6969
.flatMap(.merge, createProducer)
7070
}
7171

@@ -97,16 +97,14 @@ extension Effect {
9797
public static func future(
9898
_ attemptToFulfill: @escaping (@escaping (Result<Value, Error>) -> Void) -> Void
9999
) -> Effect {
100-
return deferred { () -> SignalProducer<Value, Error> in
101-
return SignalProducer { observer, _ in
102-
attemptToFulfill { result in
103-
switch result {
104-
case let .success(value):
105-
observer.send(value: value)
106-
observer.sendCompleted()
107-
case let .failure(error):
108-
observer.send(error: error)
109-
}
100+
SignalProducer { observer, _ in
101+
attemptToFulfill { result in
102+
switch result {
103+
case let .success(value):
104+
observer.send(value: value)
105+
observer.sendCompleted()
106+
case let .failure(error):
107+
observer.send(error: error)
110108
}
111109
}
112110
}

Sources/ComposableArchitecture/Effects/Cancellation.swift

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
11
import Foundation
22
import ReactiveSwift
33

4-
extension AnyDisposable: Hashable {
5-
public static func == (lhs: AnyDisposable, rhs: AnyDisposable) -> Bool {
6-
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
7-
}
8-
9-
public func hash(into hasher: inout Hasher) {
10-
hasher.combine(ObjectIdentifier(self))
11-
}
12-
}
13-
14-
extension AnyDisposable {
15-
/// Stores this AnyDisposable in the specified collection.
4+
extension Disposable {
5+
/// Adds this Disposable to the specified CompositeDisposable
166
/// Parameters:
17-
/// - collection: The collection to store this AnyCancellable.
18-
public func store<Disposables: RangeReplaceableCollection>(
19-
in collection: inout Disposables
20-
) where Disposables.Element == AnyDisposable {
21-
collection.append(self)
22-
}
23-
24-
/// Stores this AnyCancellable in the specified set.
25-
/// Parameters:
26-
/// - set: The set to store this AnyCancellable.
27-
public func store(in set: inout Set<AnyDisposable>) {
28-
set.insert(self)
7+
/// - composite: The CompositeDisposable to which to add this Disposable.
8+
internal func add(to composite: inout CompositeDisposable) {
9+
composite.add(self)
2910
}
3011
}
3112

@@ -58,37 +39,32 @@ extension Effect {
5839
return .deferred { () -> SignalProducer<Value, Error> in
5940
let subject = Signal<Value, Error>.pipe()
6041

61-
var disposable: Disposable?
6242
var values: [Value] = []
6343
var isCaching = true
6444

6545
cancellablesLock.sync {
6646
if cancelInFlight {
67-
cancellationCancellables[id]?.forEach { cancellable in cancellable.dispose() }
47+
cancellationCancellables[id]?.dispose()
6848
cancellationCancellables[id] = nil
6949
}
7050

71-
disposable =
72-
self
51+
let disposable = self
7352
.on {
7453
guard isCaching else { return }
7554
values.append($0)
7655
}
7756
.start(subject.input)
7857

79-
AnyDisposable {
80-
disposable?.dispose()
81-
subject.input.sendCompleted()
82-
}
83-
.store(in: &cancellationCancellables[id, default: []])
58+
disposable.add(to: &cancellationCancellables[id, default: CompositeDisposable()])
8459
}
8560

8661
func cleanUp() {
87-
disposable?.dispose()
8862
cancellablesLock.sync {
8963
guard !isCancelling.contains(id) else { return }
9064
isCancelling.insert(id)
9165
defer { isCancelling.remove(id) }
66+
67+
cancellationCancellables[id]?.dispose()
9268
cancellationCancellables[id] = nil
9369
}
9470
}
@@ -115,13 +91,13 @@ extension Effect {
11591
public static func cancel(id: AnyHashable) -> Effect {
11692
.fireAndForget {
11793
cancellablesLock.sync {
118-
cancellationCancellables[id]?.forEach { cancellable in cancellable.dispose() }
94+
cancellationCancellables[id]?.dispose()
11995
cancellationCancellables[id] = nil
12096
}
12197
}
12298
}
12399
}
124100

125-
var cancellationCancellables: [AnyHashable: Set<AnyDisposable>] = [:]
101+
var cancellationCancellables: [AnyHashable: CompositeDisposable] = [:]
126102
let cancellablesLock = NSRecursiveLock()
127103
var isCancelling: Set<AnyHashable> = []

Sources/ComposableArchitecture/Store.swift

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import ReactiveSwift
88
/// the `scope` method to derive more focused stores that can be passed to subviews.
99
public final class Store<State, Action> {
1010
@MutableProperty private(set) var state: State
11-
var effectCancellables: [UUID: Disposable] = [:]
1211
private var isSending = false
13-
private var parentCancellable: Disposable?
1412
private let reducer: (inout State, Action) -> Effect<Action, Never>
1513
private var synchronousActionsToSend: [Action] = []
1614

@@ -67,7 +65,7 @@ public final class Store<State, Action> {
6765
return .none
6866
}
6967
)
70-
localStore.parentCancellable = self.$state.producer
68+
self.$state.producer
7169
.startWithValues { [weak localStore] newValue in localStore?.state = toLocalState(newValue) }
7270
return localStore
7371
}
@@ -111,7 +109,7 @@ public final class Store<State, Action> {
111109
return .none
112110
})
113111

114-
localStore.parentCancellable = self.$state.producer
112+
self.$state.producer
115113
.startWithValues { [weak localStore] state in
116114
guard let localStore = localStore else { return }
117115
localStore.state = extractLocalState(state) ?? localStore.state
@@ -147,30 +145,16 @@ public final class Store<State, Action> {
147145
let effect = self.reducer(&self.state, action)
148146
self.isSending = false
149147

150-
var didComplete = false
151-
let uuid = UUID()
152-
153148
var isProcessingEffects = true
154-
let effectCancellable = effect.start { [weak self] event in
155-
switch event {
156-
case .completed, .interrupted:
157-
didComplete = true
158-
self?.effectCancellables[uuid] = nil
159-
160-
case let .value(action):
161-
if isProcessingEffects {
162-
self?.synchronousActionsToSend.append(action)
163-
} else {
164-
self?.send(action)
165-
}
149+
effect.startWithValues { [weak self] action in
150+
if isProcessingEffects {
151+
self?.synchronousActionsToSend.append(action)
152+
} else {
153+
self?.send(action)
166154
}
167155
}
168156
isProcessingEffects = false
169157

170-
if !didComplete {
171-
self.effectCancellables[uuid] = effectCancellable
172-
}
173-
174158
while !self.synchronousActionsToSend.isEmpty {
175159
let action = self.synchronousActionsToSend.removeFirst()
176160
self.send(action)

Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ extension Store {
1212
///
1313
/// class MasterViewController: UIViewController {
1414
/// let store: Store<MasterState, MasterAction>
15-
/// var cancellables: Set<AnyCancellable> = []
1615
/// ...
1716
/// func viewDidLoad() {
1817
/// ...
@@ -30,7 +29,6 @@ extension Store {
3029
/// self.navigationController?.popToViewController(self, animated: true)
3130
/// }
3231
/// )
33-
/// .store(in: &self.cancellables)
3432
/// }
3533
/// }
3634
///

Tests/ComposableArchitectureTests/EffectCancellationTests.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,24 +217,22 @@ final class EffectCancellationTests: XCTestCase {
217217
}
218218
.cancellable(id: 1)
219219

220-
for _ in 1 ... .random(in: 1...1_000) {
220+
for _ in 1 ... 500 {
221221
effect = effect.cancellable(id: 1)
222222
}
223223

224-
let composite = CompositeDisposable()
225-
composite.add(effect.start())
224+
let disposable = effect.start()
225+
disposable.dispose()
226226

227-
composite.dispose()
228-
229-
XCTAssertEqual([:], cancellationCancellables)
227+
XCTAssertTrue(cancellationCancellables.isEmpty)
230228
XCTAssertEqual([], isCancelling)
231229
}
232230
}
233231

234232
func resetCancellables() {
235233
cancellablesLock.sync {
236234
for (id, _) in cancellationCancellables {
237-
cancellationCancellables[id] = []
235+
cancellationCancellables.removeValue(forKey: id)
238236
}
239237
cancellationCancellables = [:]
240238
}

0 commit comments

Comments
 (0)