Skip to content

Commit 11b6658

Browse files
committed
Pullback all case studies into mega reducer (#204)
* wip * wip * fixes * clean up * clean up * clean up * clean up
1 parent 1df8ff9 commit 11b6658

17 files changed

+496
-318
lines changed

Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
CAA9ADCA2446605B0003A984 /* 02-Effects-LongLiving.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA9ADC92446605B0003A984 /* 02-Effects-LongLiving.swift */; };
2727
CAA9ADCC2446615B0003A984 /* 02-Effects-LongLivingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA9ADCB2446615B0003A984 /* 02-Effects-LongLivingTests.swift */; };
2828
CAE962FD24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift */; };
29+
CAF069D024ACC5AF00A1AAEF /* 00-Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF069CF24ACC5AF00A1AAEF /* 00-Core.swift */; };
2930
DC07231724465D1E003A8B65 /* 02-Effects-TimersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC07231624465D1E003A8B65 /* 02-Effects-TimersTests.swift */; };
3031
DC072322244663B1003A8B65 /* 03-Navigation-Sheet-LoadThenPresent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC072321244663B1003A8B65 /* 03-Navigation-Sheet-LoadThenPresent.swift */; };
3132
DC13940E2469E25C00EE1157 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = DC13940D2469E25C00EE1157 /* ComposableArchitecture */; };
@@ -146,6 +147,7 @@
146147
CAA9ADC92446605B0003A984 /* 02-Effects-LongLiving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-LongLiving.swift"; sourceTree = "<group>"; };
147148
CAA9ADCB2446615B0003A984 /* 02-Effects-LongLivingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-LongLivingTests.swift"; sourceTree = "<group>"; };
148149
CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "01-GettingStarted-AlertsAndActionSheets.swift"; sourceTree = "<group>"; };
150+
CAF069CF24ACC5AF00A1AAEF /* 00-Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "00-Core.swift"; sourceTree = "<group>"; };
149151
DC07231624465D1E003A8B65 /* 02-Effects-TimersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-TimersTests.swift"; sourceTree = "<group>"; };
150152
DC072321244663B1003A8B65 /* 03-Navigation-Sheet-LoadThenPresent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "03-Navigation-Sheet-LoadThenPresent.swift"; sourceTree = "<group>"; };
151153
DC25DC5E2450F13200082E81 /* IfLetStoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IfLetStoreController.swift; sourceTree = "<group>"; };
@@ -311,6 +313,7 @@
311313
isa = PBXGroup;
312314
children = (
313315
DC89C42424460F96006900B9 /* Info.plist */,
316+
CAF069CF24ACC5AF00A1AAEF /* 00-Core.swift */,
314317
DC89C41A24460F95006900B9 /* 00-RootView.swift */,
315318
CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift */,
316319
DC88D8A5245341EC0077F427 /* 01-GettingStarted-Animations.swift */,
@@ -579,6 +582,7 @@
579582
DCC68EE12447C4630037F998 /* 01-GettingStarted-Composition-TwoCounters.swift in Sources */,
580583
DC072322244663B1003A8B65 /* 03-Navigation-Sheet-LoadThenPresent.swift in Sources */,
581584
DC89C45324465452006900B9 /* 03-Navigation-Lists-NavigateAndLoad.swift in Sources */,
585+
CAF069D024ACC5AF00A1AAEF /* 00-Core.swift in Sources */,
582586
DCC68EE32447C8540037F998 /* 04-HigherOrderReducers-ReusableFavoriting.swift in Sources */,
583587
DCC68EDF2447BC810037F998 /* TemplateText.swift in Sources */,
584588
DCAC2A4F2452352E0094DEF5 /* 04-HigherOrderReducers-ElmLikeSubscriptions.swift in Sources */,
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
import Combine
2+
import ComposableArchitecture
3+
import UIKit
4+
5+
struct RootState {
6+
var alertAndActionSheet = AlertAndSheetState()
7+
var animation = AnimationsState()
8+
var bindingBasics = BindingBasicsState()
9+
var clock = ClockState()
10+
var counter = CounterState()
11+
var dieRoll = DieRollState()
12+
var effectsBasics = EffectsBasicsState()
13+
var effectsCancellation = EffectsCancellationState()
14+
var effectsTimers = TimersState()
15+
var episodes = EpisodesState(episodes: .mocks)
16+
var loadThenNavigate = LoadThenNavigateState()
17+
var loadThenNavigateList = LoadThenNavigateListState()
18+
var loadThenPresent = LoadThenPresentState()
19+
var longLivingEffects = LongLivingEffectsState()
20+
var map = MapAppState(cityMaps: .mocks)
21+
var multipleDependencies = MultipleDependenciesState()
22+
var navigateAndLoad = NavigateAndLoadState()
23+
var navigateAndLoadList = NavigateAndLoadListState()
24+
var nested = NestedState.mock
25+
var optionalBasics = OptionalBasicsState()
26+
var presentAndLoad = PresentAndLoadState()
27+
var shared = SharedState()
28+
var timers = TimersState()
29+
var twoCounters = TwoCountersState()
30+
var webSocket = WebSocketState()
31+
}
32+
33+
enum RootAction {
34+
case alertAndActionSheet(AlertAndSheetAction)
35+
case animation(AnimationsAction)
36+
case bindingBasics(BindingBasicsAction)
37+
case clock(ClockAction)
38+
case counter(CounterAction)
39+
case dieRoll(DieRollAction)
40+
case effectsBasics(EffectsBasicsAction)
41+
case effectsCancellation(EffectsCancellationAction)
42+
case episodes(EpisodesAction)
43+
case loadThenNavigate(LoadThenNavigateAction)
44+
case loadThenNavigateList(LoadThenNavigateListAction)
45+
case loadThenPresent(LoadThenPresentAction)
46+
case longLivingEffects(LongLivingEffectsAction)
47+
case map(MapAppAction)
48+
case multipleDependencies(MultipleDependenciesAction)
49+
case navigateAndLoad(NavigateAndLoadAction)
50+
case navigateAndLoadList(NavigateAndLoadListAction)
51+
case nested(NestedAction)
52+
case optionalBasics(OptionalBasicsAction)
53+
case onAppear
54+
case presentAndLoad(PresentAndLoadAction)
55+
case shared(SharedStateAction)
56+
case timers(TimersAction)
57+
case twoCounters(TwoCountersAction)
58+
case webSocket(WebSocketAction)
59+
}
60+
61+
struct RootEnvironment {
62+
var date: () -> Date
63+
var downloadClient: DownloadClient
64+
var favorite: (UUID, Bool) -> Effect<Bool, Error>
65+
var fetchNumber: () -> Effect<Int, Never>
66+
var mainQueue: AnySchedulerOf<DispatchQueue>
67+
var numberFact: (Int) -> Effect<String, NumbersApiError>
68+
var trivia: (Int) -> Effect<String, TriviaApiError>
69+
var userDidTakeScreenshot: Effect<Void, Never>
70+
var uuid: () -> UUID
71+
var webSocket: WebSocketClient
72+
73+
static let live = Self(
74+
date: Date.init,
75+
downloadClient: .live,
76+
favorite: favorite(id:isFavorite:),
77+
fetchNumber: liveFetchNumber,
78+
mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
79+
numberFact: liveNumberFact(for:),
80+
trivia: liveTrivia(for:),
81+
userDidTakeScreenshot: liveUserDidTakeScreenshot,
82+
uuid: UUID.init,
83+
webSocket: .live
84+
)
85+
}
86+
87+
let rootReducer = Reducer<RootState, RootAction, RootEnvironment>.combine(
88+
.init { state, action, _ in
89+
switch action {
90+
case .onAppear:
91+
state = .init()
92+
return .none
93+
94+
default:
95+
return .none
96+
}
97+
},
98+
alertAndSheetReducer
99+
.pullback(
100+
state: \.alertAndActionSheet,
101+
action: /RootAction.alertAndActionSheet,
102+
environment: { _ in .init() }
103+
),
104+
animationsReducer
105+
.pullback(
106+
state: \.animation,
107+
action: /RootAction.animation,
108+
environment: { .init(mainQueue: $0.mainQueue) }
109+
),
110+
bindingBasicsReducer
111+
.pullback(
112+
state: \.bindingBasics,
113+
action: /RootAction.bindingBasics,
114+
environment: { _ in .init() }
115+
),
116+
clockReducer
117+
.pullback(
118+
state: \.clock,
119+
action: /RootAction.clock,
120+
environment: { .init(mainQueue: $0.mainQueue) }
121+
),
122+
counterReducer
123+
.pullback(
124+
state: \.counter,
125+
action: /RootAction.counter,
126+
environment: { _ in .init() }
127+
),
128+
dieRollReducer
129+
.pullback(
130+
state: \.dieRoll,
131+
action: /RootAction.dieRoll,
132+
environment: { _ in .init(rollDie: { .random(in: 1...6) }) }
133+
),
134+
effectsBasicsReducer
135+
.pullback(
136+
state: \.effectsBasics,
137+
action: /RootAction.effectsBasics,
138+
environment: { .init(mainQueue: $0.mainQueue, numberFact: $0.numberFact) }
139+
),
140+
effectsCancellationReducer
141+
.pullback(
142+
state: \.effectsCancellation,
143+
action: /RootAction.effectsCancellation,
144+
environment: { .init(mainQueue: $0.mainQueue, trivia: $0.trivia) }
145+
),
146+
episodesReducer
147+
.pullback(
148+
state: \.episodes,
149+
action: /RootAction.episodes,
150+
environment: { .init(favorite: $0.favorite, mainQueue: $0.mainQueue) }
151+
),
152+
loadThenNavigateReducer
153+
.pullback(
154+
state: \.loadThenNavigate,
155+
action: /RootAction.loadThenNavigate,
156+
environment: { .init(mainQueue: $0.mainQueue) }
157+
),
158+
loadThenNavigateListReducer
159+
.pullback(
160+
state: \.loadThenNavigateList,
161+
action: /RootAction.loadThenNavigateList,
162+
environment: { .init(mainQueue: $0.mainQueue) }
163+
),
164+
loadThenPresentReducer
165+
.pullback(
166+
state: \.loadThenPresent,
167+
action: /RootAction.loadThenPresent,
168+
environment: { .init(mainQueue: $0.mainQueue) }
169+
),
170+
longLivingEffectsReducer
171+
.pullback(
172+
state: \.longLivingEffects,
173+
action: /RootAction.longLivingEffects,
174+
environment: { .init(userDidTakeScreenshot: $0.userDidTakeScreenshot) }
175+
),
176+
mapAppReducer
177+
.pullback(
178+
state: \.map,
179+
action: /RootAction.map,
180+
environment: { .init(downloadClient: $0.downloadClient, mainQueue: $0.mainQueue) }
181+
),
182+
multipleDependenciesReducer
183+
.pullback(
184+
state: \.multipleDependencies,
185+
action: /RootAction.multipleDependencies,
186+
environment: { env in
187+
.init(
188+
date: env.date,
189+
environment: .init(fetchNumber: env.fetchNumber),
190+
mainQueue: { env.mainQueue },
191+
uuid: env.uuid
192+
)
193+
}
194+
),
195+
navigateAndLoadReducer
196+
.pullback(
197+
state: \.navigateAndLoad,
198+
action: /RootAction.navigateAndLoad,
199+
environment: { .init(mainQueue: $0.mainQueue) }
200+
),
201+
navigateAndLoadListReducer
202+
.pullback(
203+
state: \.navigateAndLoadList,
204+
action: /RootAction.navigateAndLoadList,
205+
environment: { .init(mainQueue: $0.mainQueue) }
206+
),
207+
nestedReducer
208+
.pullback(
209+
state: \.nested,
210+
action: /RootAction.nested,
211+
environment: { .init(uuid: $0.uuid) }
212+
),
213+
optionalBasicsReducer
214+
.pullback(
215+
state: \.optionalBasics,
216+
action: /RootAction.optionalBasics,
217+
environment: { _ in .init() }
218+
),
219+
presentAndLoadReducer
220+
.pullback(
221+
state: \.presentAndLoad,
222+
action: /RootAction.presentAndLoad,
223+
environment: { .init(mainQueue: $0.mainQueue) }
224+
),
225+
sharedStateReducer
226+
.pullback(
227+
state: \.shared,
228+
action: /RootAction.shared,
229+
environment: { _ in () }
230+
),
231+
timersReducer
232+
.pullback(
233+
state: \.timers,
234+
action: /RootAction.timers,
235+
environment: { .init(mainQueue: $0.mainQueue) }
236+
),
237+
twoCountersReducer
238+
.pullback(
239+
state: \.twoCounters,
240+
action: /RootAction.twoCounters,
241+
environment: { _ in .init() }
242+
),
243+
webSocketReducer
244+
.pullback(
245+
state: \.webSocket,
246+
action: /RootAction.webSocket,
247+
environment: { .init(mainQueue: $0.mainQueue, webSocket: $0.webSocket) }
248+
)
249+
)
250+
.signpost()
251+
252+
// This is the "live" trivia dependency that reaches into the outside world to fetch trivia.
253+
// Typically this live implementation of the dependency would live in its own module so that the
254+
// main feature doesn't need to compile it.
255+
func liveNumberFact(for n: Int) -> Effect<String, NumbersApiError> {
256+
return URLSession.shared.dataTaskPublisher(for: URL(string: "http://numbersapi.com/\(n)/trivia")!)
257+
.map { data, _ in String(decoding: data, as: UTF8.self) }
258+
.catch { _ in
259+
// Sometimes numbersapi.com can be flakey, so if it ever fails we will just
260+
// default to a mock response.
261+
Just("\(n) is a good number Brent")
262+
.delay(for: 1, scheduler: DispatchQueue.main)
263+
}
264+
.mapError { _ in NumbersApiError() }
265+
.eraseToEffect()
266+
}
267+
268+
// This is the "live" trivia dependency that reaches into the outside world to fetch trivia.
269+
// Typically this live implementation of the dependency would live in its own module so that the
270+
// main feature doesn't need to compile it.
271+
func liveTrivia(for n: Int) -> Effect<String, TriviaApiError> {
272+
URLSession.shared.dataTaskPublisher(for: URL(string: "http://numbersapi.com/\(n)/trivia")!)
273+
.map { data, _ in String.init(decoding: data, as: UTF8.self) }
274+
.catch { _ in
275+
// Sometimes numbersapi.com can be flakey, so if it ever fails we will just
276+
// default to a mock response.
277+
Just("\(n) is a good number Brent")
278+
.delay(for: 1, scheduler: DispatchQueue.main)
279+
}
280+
.mapError { _ in TriviaApiError() }
281+
.eraseToEffect()
282+
}
283+
284+
private func liveFetchNumber() -> Effect<Int, Never> {
285+
Deferred { Just(Int.random(in: 1...1_000)) }
286+
.delay(for: 1, scheduler: DispatchQueue.main)
287+
.eraseToEffect()
288+
}
289+
290+
private let liveUserDidTakeScreenshot = NotificationCenter.default
291+
.publisher(for: UIApplication.userDidTakeScreenshotNotification)
292+
.map { _ in () }
293+
.eraseToEffect()

0 commit comments

Comments
 (0)