Skip to content

Commit 6b0a4ce

Browse files
Add Identifiable copy for Linux (#29)
* Add Identifiable copy for Linux * Add Tests for Identifiable * Apply changes after PR review * Comment indetifiable protocol * Remove Identifiable.swift
1 parent a53573e commit 6b0a4ce

File tree

4 files changed

+376
-380
lines changed

4 files changed

+376
-380
lines changed

Sources/ComposableArchitecture/Reducer.swift

Lines changed: 63 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -405,58 +405,57 @@ public struct Reducer<State, Action, Environment> {
405405
.map { toLocalAction.embed((index, $0)) }
406406
}
407407
}
408-
409-
#if canImport(SwiftUI)
410-
/// A version of `pullback` that transforms a reducer that works on an element into one that works
411-
/// on an identified array of elements.
412-
///
413-
/// // Global domain that holds a collection of local domains:
414-
/// struct AppState { var todos: IdentifiedArrayOf<Todo> }
415-
/// struct AppAction { case todo(id: Todo.ID, action: TodoAction) }
416-
/// struct AppEnvironment { var mainQueue: DateScheduler }
417-
///
418-
/// // A reducer that works on a local domain:
419-
/// let todoReducer = Reducer<Todo, TodoAction, TodoEnvironment> { ... }
420-
///
421-
/// // Pullback the local todo reducer so that it works on all of the app domain:
422-
/// let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
423-
/// todoReducer.forEach(
424-
/// state: \.todos,
425-
/// action: /AppAction.todo(id:action:),
426-
/// environment: { _ in TodoEnvironment() }
427-
/// ),
428-
/// Reducer { state, action, environment in
429-
/// ...
430-
/// }
431-
/// )
432-
///
433-
/// Take care when combining `forEach` reducers into parent domains, as order matters. Always
434-
/// combine `forEach` reducers _before_ parent reducers that can modify the collection.
435-
///
436-
/// - Parameters:
437-
/// - toLocalState: A key path that can get/set a collection of `State` elements inside
438-
/// `GlobalState`.
439-
/// - toLocalAction: A case path that can extract/embed `(Collection.Index, Action)` from
440-
/// `GlobalAction`.
441-
/// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`.
442-
/// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`.
443-
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
444-
public func forEach<GlobalState, GlobalAction, GlobalEnvironment, ID>(
445-
state toLocalState: WritableKeyPath<GlobalState, IdentifiedArray<ID, State>>,
446-
action toLocalAction: CasePath<GlobalAction, (ID, Action)>,
447-
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
448-
_ file: StaticString = #file,
449-
_ line: UInt = #line
450-
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment> {
451-
.init { globalState, globalAction, globalEnvironment in
452-
guard let (id, localAction) = toLocalAction.extract(from: globalAction) else {
453-
return .none
454-
}
455-
456-
// This does not need to be a fatal error because of the unwrap that follows it.
457-
assert(
458-
globalState[keyPath: toLocalState][id: id] != nil,
459-
"""
408+
409+
/// A version of `pullback` that transforms a reducer that works on an element into one that works
410+
/// on an identified array of elements.
411+
///
412+
/// // Global domain that holds a collection of local domains:
413+
/// struct AppState { var todos: IdentifiedArrayOf<Todo> }
414+
/// struct AppAction { case todo(id: Todo.ID, action: TodoAction) }
415+
/// struct AppEnvironment { var mainQueue: DateScheduler }
416+
///
417+
/// // A reducer that works on a local domain:
418+
/// let todoReducer = Reducer<Todo, TodoAction, TodoEnvironment> { ... }
419+
///
420+
/// // Pullback the local todo reducer so that it works on all of the app domain:
421+
/// let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
422+
/// todoReducer.forEach(
423+
/// state: \.todos,
424+
/// action: /AppAction.todo(id:action:),
425+
/// environment: { _ in TodoEnvironment() }
426+
/// ),
427+
/// Reducer { state, action, environment in
428+
/// ...
429+
/// }
430+
/// )
431+
///
432+
/// Take care when combining `forEach` reducers into parent domains, as order matters. Always
433+
/// combine `forEach` reducers _before_ parent reducers that can modify the collection.
434+
///
435+
/// - Parameters:
436+
/// - toLocalState: A key path that can get/set a collection of `State` elements inside
437+
/// `GlobalState`.
438+
/// - toLocalAction: A case path that can extract/embed `(Collection.Index, Action)` from
439+
/// `GlobalAction`.
440+
/// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`.
441+
/// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`.
442+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
443+
public func forEach<GlobalState, GlobalAction, GlobalEnvironment, ID>(
444+
state toLocalState: WritableKeyPath<GlobalState, IdentifiedArray<ID, State>>,
445+
action toLocalAction: CasePath<GlobalAction, (ID, Action)>,
446+
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
447+
_ file: StaticString = #file,
448+
_ line: UInt = #line
449+
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment> {
450+
.init { globalState, globalAction, globalEnvironment in
451+
guard let (id, localAction) = toLocalAction.extract(from: globalAction) else {
452+
return .none
453+
}
454+
455+
// This does not need to be a fatal error because of the unwrap that follows it.
456+
assert(
457+
globalState[keyPath: toLocalState][id: id] != nil,
458+
"""
460459
"\(debugCaseOutput(localAction))" was received by a "forEach" reducer at id \(id) \
461460
when its state contained no element at this id. This is considered an application logic \
462461
error, and can happen for a few reasons:
@@ -476,21 +475,20 @@ public struct Reducer<State, Action, Environment> {
476475
To fix this make sure that actions for this reducer can only be sent to a view store when \
477476
its state contains an element at this id. In SwiftUI applications, use `ForEachStore`.
478477
""",
479-
file: file,
480-
line: line
478+
file: file,
479+
line: line
480+
)
481+
482+
return
483+
self
484+
.reducer(
485+
&globalState[keyPath: toLocalState][id: id]!,
486+
localAction,
487+
toLocalEnvironment(globalEnvironment)
481488
)
482-
483-
return
484-
self
485-
.reducer(
486-
&globalState[keyPath: toLocalState][id: id]!,
487-
localAction,
488-
toLocalEnvironment(globalEnvironment)
489-
)
490-
.map { toLocalAction.embed((id, $0)) }
491-
}
489+
.map { toLocalAction.embed((id, $0)) }
492490
}
493-
#endif
491+
}
494492

495493
/// A version of `pullback` that transforms a reducer that works on an element into one that works
496494
/// on a dictionary of element values.
Lines changed: 51 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,56 @@
1-
#if canImport(SwiftUI)
2-
/// A wrapper around a value and a hashable identifier that conforms to identifiable.
3-
@dynamicMemberLookup
4-
public struct Identified<ID, Value>: Identifiable where ID: Hashable {
5-
public let id: ID
6-
public var value: Value
7-
8-
/// Initializes an identified value from a given value and a hashable identifier.
9-
///
10-
/// - Parameters:
11-
/// - value: A value.
12-
/// - id: A hashable identifier.
13-
public init(_ value: Value, id: ID) {
14-
self.id = id
15-
self.value = value
16-
}
17-
18-
/// Initializes an identified value from a given value and a function that can return a hashable
19-
/// identifier from the value.
20-
///
21-
/// Identified(uuid, id: \.self)
22-
///
23-
/// - Parameters:
24-
/// - value: A value.
25-
/// - id: A hashable identifier.
26-
public init(_ value: Value, id: (Value) -> ID) {
27-
self.init(value, id: id(value))
28-
}
29-
30-
// NB: This overload works around a bug in key path function expressions and `\.self`.
31-
/// Initializes an identified value from a given value and a function that can return a hashable
32-
/// identifier from the value.
33-
///
34-
/// Identified(uuid, id: \.self)
35-
///
36-
/// - Parameters:
37-
/// - value: A value.
38-
/// - id: A key path from the value to a hashable identifier.
39-
public init(_ value: Value, id: KeyPath<Value, ID>) {
40-
self.init(value, id: value[keyPath: id])
41-
}
42-
43-
public subscript<LocalValue>(
44-
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
45-
) -> LocalValue {
46-
get { self.value[keyPath: keyPath] }
47-
set { self.value[keyPath: keyPath] = newValue }
48-
}
1+
/// A wrapper around a value and a hashable identifier that conforms to identifiable.
2+
@dynamicMemberLookup
3+
public struct Identified<ID, Value>: Identifiable where ID: Hashable {
4+
public let id: ID
5+
public var value: Value
6+
7+
/// Initializes an identified value from a given value and a hashable identifier.
8+
///
9+
/// - Parameters:
10+
/// - value: A value.
11+
/// - id: A hashable identifier.
12+
public init(_ value: Value, id: ID) {
13+
self.id = id
14+
self.value = value
15+
}
16+
17+
/// Initializes an identified value from a given value and a function that can return a hashable
18+
/// identifier from the value.
19+
///
20+
/// Identified(uuid, id: \.self)
21+
///
22+
/// - Parameters:
23+
/// - value: A value.
24+
/// - id: A hashable identifier.
25+
public init(_ value: Value, id: (Value) -> ID) {
26+
self.init(value, id: id(value))
27+
}
28+
29+
// NB: This overload works around a bug in key path function expressions and `\.self`.
30+
/// Initializes an identified value from a given value and a function that can return a hashable
31+
/// identifier from the value.
32+
///
33+
/// Identified(uuid, id: \.self)
34+
///
35+
/// - Parameters:
36+
/// - value: A value.
37+
/// - id: A key path from the value to a hashable identifier.
38+
public init(_ value: Value, id: KeyPath<Value, ID>) {
39+
self.init(value, id: value[keyPath: id])
40+
}
41+
42+
public subscript<LocalValue>(
43+
dynamicMember keyPath: WritableKeyPath<Value, LocalValue>
44+
) -> LocalValue {
45+
get { self.value[keyPath: keyPath] }
46+
set { self.value[keyPath: keyPath] = newValue }
4947
}
48+
}
5049

51-
extension Identified: Decodable where ID: Decodable, Value: Decodable {}
50+
extension Identified: Decodable where ID: Decodable, Value: Decodable {}
5251

53-
extension Identified: Encodable where ID: Encodable, Value: Encodable {}
52+
extension Identified: Encodable where ID: Encodable, Value: Encodable {}
5453

55-
extension Identified: Equatable where Value: Equatable {}
54+
extension Identified: Equatable where Value: Equatable {}
5655

57-
extension Identified: Hashable where Value: Hashable {}
58-
#endif
56+
extension Identified: Hashable where Value: Hashable {}

0 commit comments

Comments
 (0)