Skip to content

Commit fbb68d9

Browse files
mbrandonwp4checo
authored andcommitted
Remove some escaping closures (#1493)
* Remove some escaping closures. * wip * wip * Add some canary tests to track down failures. * Remove a few more escaping closures. (cherry picked from commit 7b7a97fb837a43cb864d99434cbfd3816c21193e) # Conflicts: # Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift # Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift # Sources/ComposableArchitecture/SwiftUI/SwitchStore.swift # Tests/ComposableArchitectureTests/EffectTests.swift
1 parent bb53a7e commit fbb68d9

File tree

6 files changed

+1407
-1391
lines changed

6 files changed

+1407
-1391
lines changed

Sources/ComposableArchitecture/Internal/Deprecations.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,16 +1164,14 @@ extension ForEachStore {
11641164
{
11651165
let data = store.state
11661166
self.data = data
1167-
self.content = {
1168-
WithViewStore(store.scope(state: { $0.map { $0[keyPath: id] } })) { viewStore in
1169-
ForEach(Array(viewStore.state.enumerated()), id: \.element) { index, _ in
1170-
content(
1171-
store.scope(
1172-
state: { index < $0.endIndex ? $0[index] : data[index] },
1173-
action: { (index, $0) }
1174-
)
1167+
self.content = WithViewStore(store.scope(state: { $0.map { $0[keyPath: id] } })) { viewStore in
1168+
ForEach(Array(viewStore.state.enumerated()), id: \.element) { index, _ in
1169+
content(
1170+
store.scope(
1171+
state: { index < $0.endIndex ? $0[index] : data[index] },
1172+
action: { (index, $0) }
11751173
)
1176-
}
1174+
)
11771175
}
11781176
}
11791177
}
Lines changed: 129 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,143 @@
11
#if canImport(SwiftUI)
2-
import OrderedCollections
3-
import SwiftUI
2+
import OrderedCollections
3+
import SwiftUI
44

5-
/// A Composable Architecture-friendly wrapper around `ForEach` that simplifies working with
6-
/// collections of state.
7-
///
8-
/// ``ForEachStore`` loops over a store's collection with a store scoped to the domain of each
9-
/// element. This allows you to extract and modularize an element's view and avoid concerns around
10-
/// collection index math and parent-child store communication.
11-
///
12-
/// For example, a todos app may define the domain and logic associated with an individual todo:
13-
///
14-
/// ```swift
15-
/// struct Todo: ReducerProtocol {
16-
/// struct State: Equatable, Identifiable {
17-
/// let id: UUID
18-
/// var description = ""
19-
/// var isComplete = false
20-
/// }
21-
///
22-
/// enum Action {
23-
/// case isCompleteToggled(Bool)
24-
/// case descriptionChanged(String)
25-
/// }
26-
///
27-
/// func reduce(into state: inout State, action: Action) -> Effect<Action, Never> { ... }
28-
/// }
29-
/// ```
30-
///
31-
/// As well as a view with a domain-specific store:
32-
///
33-
/// ```swift
34-
/// struct TodoView: View {
35-
/// let store: StoreOf<Todo>
36-
/// var body: some View { ... }
37-
/// }
38-
/// ```
39-
///
40-
/// For a parent domain to work with a collection of todos, it can hold onto this collection in
41-
/// state:
42-
///
43-
/// ```swift
44-
/// struct Todos: ReducerProtocol { {
45-
/// struct State: Equatable {
46-
/// var todos: IdentifiedArrayOf<TodoState> = []
47-
/// }
48-
/// ```
49-
///
50-
/// Define a case to handle actions sent to the child domain:
51-
///
52-
/// ```swift
53-
/// enum Action {
54-
/// case todo(id: TodoState.ID, action: TodoAction)
55-
/// }
56-
/// ```
57-
///
58-
/// Enhance its core reducer using ``ReducerProtocol/forEach(_:action:_:file:fileID:line:)``:
59-
///
60-
/// ```swift
61-
/// var body: some ReducerProtocol<State, Action> {
62-
/// Reduce { state, action in
63-
/// ...
64-
/// }
65-
/// .forEach(state: \.todos, action: /Action.todo(id:action:)) {
66-
/// Todo()
67-
/// }
68-
/// }
69-
/// ```
70-
///
71-
/// And finally render a list of `TodoView`s using ``ForEachStore``:
72-
///
73-
/// ```swift
74-
/// ForEachStore(
75-
/// self.store.scope(state: \.todos, AppAction.todo(id:action:))
76-
/// ) { todoStore in
77-
/// TodoView(store: todoStore)
78-
/// }
79-
/// ```
80-
///
81-
public struct ForEachStore<
82-
EachState, EachAction, Data: Collection, ID: Hashable, Content: View
83-
>: DynamicViewContent {
84-
public let data: Data
85-
let content: () -> Content
5+
/// A Composable Architecture-friendly wrapper around `ForEach` that simplifies working with
6+
/// collections of state.
7+
///
8+
/// ``ForEachStore`` loops over a store's collection with a store scoped to the domain of each
9+
/// element. This allows you to extract and modularize an element's view and avoid concerns around
10+
/// collection index math and parent-child store communication.
11+
///
12+
/// For example, a todos app may define the domain and logic associated with an individual todo:
13+
///
14+
/// ```swift
15+
/// struct Todo: ReducerProtocol {
16+
/// struct State: Equatable, Identifiable {
17+
/// let id: UUID
18+
/// var description = ""
19+
/// var isComplete = false
20+
/// }
21+
///
22+
/// enum Action {
23+
/// case isCompleteToggled(Bool)
24+
/// case descriptionChanged(String)
25+
/// }
26+
///
27+
/// func reduce(into state: inout State, action: Action) -> Effect<Action, Never> { ... }
28+
/// }
29+
/// ```
30+
///
31+
/// As well as a view with a domain-specific store:
32+
///
33+
/// ```swift
34+
/// struct TodoView: View {
35+
/// let store: StoreOf<Todo>
36+
/// var body: some View { ... }
37+
/// }
38+
/// ```
39+
///
40+
/// For a parent domain to work with a collection of todos, it can hold onto this collection in
41+
/// state:
42+
///
43+
/// ```swift
44+
/// struct Todos: ReducerProtocol { {
45+
/// struct State: Equatable {
46+
/// var todos: IdentifiedArrayOf<TodoState> = []
47+
/// }
48+
/// ```
49+
///
50+
/// Define a case to handle actions sent to the child domain:
51+
///
52+
/// ```swift
53+
/// enum Action {
54+
/// case todo(id: TodoState.ID, action: TodoAction)
55+
/// }
56+
/// ```
57+
///
58+
/// Enhance its core reducer using ``ReducerProtocol/forEach(_:action:_:file:fileID:line:)``:
59+
///
60+
/// ```swift
61+
/// var body: some ReducerProtocol<State, Action> {
62+
/// Reduce { state, action in
63+
/// ...
64+
/// }
65+
/// .forEach(state: \.todos, action: /Action.todo(id:action:)) {
66+
/// Todo()
67+
/// }
68+
/// }
69+
/// ```
70+
///
71+
/// And finally render a list of `TodoView`s using ``ForEachStore``:
72+
///
73+
/// ```swift
74+
/// ForEachStore(
75+
/// self.store.scope(state: \.todos, AppAction.todo(id:action:))
76+
/// ) { todoStore in
77+
/// TodoView(store: todoStore)
78+
/// }
79+
/// ```
80+
///
81+
public struct ForEachStore<
82+
EachState, EachAction, Data: Collection, ID: Hashable, Content: View
83+
>: DynamicViewContent {
84+
public let data: Data
85+
let content: Content
8686

87-
/// Initializes a structure that computes views on demand from a store on a collection of data and
88-
/// an identified action.
89-
///
90-
/// - Parameters:
91-
/// - store: A store on an identified array of data and an identified action.
92-
/// - content: A function that can generate content given a store of an element.
93-
public init<EachContent>(
94-
_ store: Store<IdentifiedArray<ID, EachState>, (ID, EachAction)>,
95-
@ViewBuilder content: @escaping (Store<EachState, EachAction>) -> EachContent
96-
)
97-
where
98-
Data == IdentifiedArray<ID, EachState>,
99-
Content == WithViewStore<
100-
OrderedSet<ID>, (ID, EachAction), ForEach<OrderedSet<ID>, ID, EachContent>
101-
>
102-
{
87+
/// Initializes a structure that computes views on demand from a store on a collection of data and
88+
/// an identified action.
89+
///
90+
/// - Parameters:
91+
/// - store: A store on an identified array of data and an identified action.
92+
/// - content: A function that can generate content given a store of an element.
93+
public init<EachContent>(
94+
_ store: Store<IdentifiedArray<ID, EachState>, (ID, EachAction)>,
95+
@ViewBuilder content: @escaping (Store<EachState, EachAction>) -> EachContent
96+
)
97+
where
98+
Data == IdentifiedArray<ID, EachState>,
99+
Content == WithViewStore<
100+
OrderedSet<ID>, (ID, EachAction), ForEach<OrderedSet<ID>, ID, EachContent>
101+
>
102+
{
103103
self.data = store.state
104-
self.content = {
105-
WithViewStore(
106-
store,
107-
observe: { $0.ids },
108-
removeDuplicates: areOrderedSetsDuplicates
109-
) { viewStore in
110-
ForEach(viewStore.state, id: \.self) { id -> EachContent in
111-
// NB: We cache elements here to avoid a potential crash where SwiftUI may re-evaluate
112-
// views for elements no longer in the collection.
113-
//
114-
// Feedback filed: https://gist.github.com/stephencelis/cdf85ae8dab437adc998fb0204ed9a6b
104+
self.content = WithViewStore(
105+
store,
106+
observe: { $0.ids },
107+
removeDuplicates: areOrderedSetsDuplicates
108+
) { viewStore in
109+
ForEach(viewStore.state, id: \.self) { id -> EachContent in
110+
// NB: We cache elements here to avoid a potential crash where SwiftUI may re-evaluate
111+
// views for elements no longer in the collection.
112+
//
113+
// Feedback filed: https://gist.github.com/stephencelis/cdf85ae8dab437adc998fb0204ed9a6b
115114
var element = store.state[id: id]!
116-
return content(
117-
store.scope(
118-
state: {
119-
element = $0[id: id] ?? element
120-
return element
121-
},
122-
action: { (id, $0) }
123-
)
115+
return content(
116+
store.scope(
117+
state: {
118+
element = $0[id: id] ?? element
119+
return element
120+
},
121+
action: { (id, $0) }
124122
)
125-
}
123+
)
126124
}
127125
}
128126
}
129127

130-
public var body: some View {
131-
self.content()
132-
}
128+
public var body: some View {
129+
self.content
133130
}
131+
}
134132

135-
private func areOrderedSetsDuplicates<ID: Hashable>(lhs: OrderedSet<ID>, rhs: OrderedSet<ID>)
136-
-> Bool
137-
{
138-
var lhs = lhs
139-
var rhs = rhs
140-
if memcmp(&lhs, &rhs, MemoryLayout<OrderedSet<ID>>.size) == 0 {
141-
return true
142-
}
143-
return lhs == rhs
133+
private func areOrderedSetsDuplicates<ID: Hashable>(lhs: OrderedSet<ID>, rhs: OrderedSet<ID>)
134+
-> Bool
135+
{
136+
var lhs = lhs
137+
var rhs = rhs
138+
if memcmp(&lhs, &rhs, MemoryLayout<OrderedSet<ID>>.size) == 0 {
139+
return true
144140
}
141+
return lhs == rhs
142+
}
145143
#endif

0 commit comments

Comments
 (0)