From 81a9c466bedd3899e8538e488eeba1619649ae3b Mon Sep 17 00:00:00 2001 From: Nguyen Phong Date: Fri, 14 Apr 2023 09:34:29 +0700 Subject: [PATCH] update upstream --- .../UserInterfaceState.xcuserstate | Bin 55409 -> 136743 bytes .../rxswift-composable-architecture.xcscheme | 67 + .../xcdebugger/Breakpoints_v2.xcbkptlist | 18 - .../xcschemes/xcschememanagement.plist | 153 +- Package.resolved | 21 +- Package.swift | 6 +- .../Debugging/ReducerInstrumentation.swift | 220 +-- .../Dependencies/Dependency.swift | 106 ++ Sources/ComposableArchitecture/Effect.swift | 2 + .../ComposableArchitecture/Internal/Box.swift | 12 + .../Internal/Exports.swift | 1 + .../Internal/OpenExistential.swift | 42 + .../Internal/RuntimeWarnings.swift | 138 +- .../Internal/TypeName.swift | 15 + Sources/ComposableArchitecture/Reducer.swift | 224 +-- .../Reducer/AnyReducer/AnyReducer.swift | 1229 +++++++++++++++++ .../AnyReducer/AnyReducerBinding.swift | 8 + .../AnyReducer/AnyReducerCompatibility.swift | 8 + .../Reducer/AnyReducer/AnyReducerDebug.swift | 8 + .../AnyReducer/AnyReducerSignpost.swift | 144 ++ .../Reducer/ReducerBuilder.swift | 381 +++++ .../Reducer/Reducers/BindingReducer.swift | 25 + .../Reducer/Reducers/CombineReducers.swift | 45 + .../Reducer/Reducers/DebugReducer.swift | 8 + .../DependencyKeyWritingReducer.swift | 8 + .../Reducer/Reducers/EmptyReducer.swift | 19 + .../Reducer/Reducers/ForEachReducer.swift | 164 +++ .../Reducer/Reducers/IfCaseLetReducer.swift | 8 + .../Reducer/Reducers/IfLetReducer.swift | 156 +++ .../Reducer/Reducers/Optional.swift | 19 + .../Reducer/Reducers/Reduce.swift | 37 + .../Reducer/Reducers/Scope.swift | 8 + .../Reducer/Reducers/SignpostReducer.swift | 8 + .../ReducerProtocol.swift | 374 +++++ Sources/ComposableArchitecture/Store.swift | 731 +++++----- .../TestSupport/FailingEffect.swift | 4 +- .../UIKit/Binding.swift | 434 ++++++ .../ComposableArchitecture/ViewStore.swift | 426 ++++-- 38 files changed, 4560 insertions(+), 717 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/rxswift-composable-architecture.xcscheme create mode 100644 Sources/ComposableArchitecture/Dependencies/Dependency.swift create mode 100644 Sources/ComposableArchitecture/Internal/Box.swift create mode 100644 Sources/ComposableArchitecture/Internal/OpenExistential.swift create mode 100644 Sources/ComposableArchitecture/Internal/TypeName.swift create mode 100644 Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerBinding.swift create mode 100644 Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerCompatibility.swift create mode 100644 Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerDebug.swift create mode 100644 Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerSignpost.swift create mode 100644 Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/BindingReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/CombineReducers.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/DebugReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/DependencyKeyWritingReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/EmptyReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/IfCaseLetReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/IfLetReducer.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/Optional.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/Reduce.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/Scope.swift create mode 100644 Sources/ComposableArchitecture/Reducer/Reducers/SignpostReducer.swift create mode 100644 Sources/ComposableArchitecture/ReducerProtocol.swift create mode 100644 Sources/ComposableArchitecture/UIKit/Binding.swift diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/nguyenphong.xcuserdatad/UserInterfaceState.xcuserstate index c6c5223f64dc1c67b3bd78020fd8af45d654836e..51fe32680bae29da6a1b45f1f1553daf2dd9aaff 100644 GIT binary patch literal 136743 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

82B1E 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
    iPWsj-=57?q-3F9<^vzEQY+B&m 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 NwEwSA{vQ$h{{f3#KDht@ diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/rxswift-composable-architecture.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/rxswift-composable-architecture.xcscheme new file mode 100644 index 0000000..894309e --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/rxswift-composable-architecture.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 30bbc88..754cc9b 100644 --- a/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,22 +3,4 @@ uuid = "C3C26736-93C2-42C2-8EBC-0079D914396C" type = "1" version = "2.0"> - - - - - - diff --git a/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist index d93a2e7..e41e710 100644 --- a/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/.swiftpm/xcode/xcuserdata/nguyenphong.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,22 +9,169 @@ isShown orderHint - 5 + 6 + + Rx (Playground) 10.xcscheme + + isShown + + orderHint + 14 + + Rx (Playground) 11.xcscheme + + isShown + + orderHint + 15 + + Rx (Playground) 12.xcscheme + + isShown + + orderHint + 16 + + Rx (Playground) 13.xcscheme + + isShown + + orderHint + 17 + + Rx (Playground) 14.xcscheme + + isShown + + orderHint + 18 + + Rx (Playground) 15.xcscheme + + isShown + + orderHint + 19 + + Rx (Playground) 16.xcscheme + + isShown + + orderHint + 20 + + Rx (Playground) 17.xcscheme + + isShown + + orderHint + 21 + + Rx (Playground) 18.xcscheme + + isShown + + orderHint + 22 + + Rx (Playground) 19.xcscheme + + isShown + + orderHint + 23 Rx (Playground) 2.xcscheme isShown orderHint - 6 + 7 - Rx (Playground).xcscheme + Rx (Playground) 20.xcscheme + + isShown + + orderHint + 24 + + Rx (Playground) 21.xcscheme + + isShown + + orderHint + 25 + + Rx (Playground) 22.xcscheme + + isShown + + orderHint + 26 + + Rx (Playground) 23.xcscheme + + isShown + + orderHint + 27 + + Rx (Playground) 3.xcscheme isShown orderHint 4 + Rx (Playground) 4.xcscheme + + isShown + + orderHint + 8 + + Rx (Playground) 5.xcscheme + + isShown + + orderHint + 9 + + Rx (Playground) 6.xcscheme + + isShown + + orderHint + 10 + + Rx (Playground) 7.xcscheme + + isShown + + orderHint + 11 + + Rx (Playground) 8.xcscheme + + isShown + + orderHint + 12 + + Rx (Playground) 9.xcscheme + + isShown + + orderHint + 13 + + Rx (Playground).xcscheme + + isShown + + orderHint + 5 + rxswift-composable-architecture.xcscheme_^#shared#^_ orderHint diff --git a/Package.resolved b/Package.resolved index c20634e..d8e7da1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", "state": { "branch": null, - "revision": "7c17a6ccca06b5c107cfa4284e634562ddaf5951", - "version": "6.2.0" + "revision": "b4307ba0b6425c0ba4178e138799946c3da594f8", + "version": "6.5.0" } }, { @@ -20,12 +20,21 @@ } }, { - "package": "xctest-dynamic-overlay", - "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", + "package": "swift-collections", + "repositoryURL": "https://github.com/apple/swift-collections", "state": { "branch": null, - "revision": "50a70a9d3583fe228ce672e8923010c8df2deddd", - "version": "0.2.1" + "revision": "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version": "1.0.4" + } + }, + { + "package": "swift-identified-collections", + "repositoryURL": "https://github.com/pointfreeco/swift-identified-collections", + "state": { + "branch": null, + "revision": "f52eee28bdc6065aa2f8424067e6f04c74bda6e6", + "version": "0.7.1" } } ] diff --git a/Package.swift b/Package.swift index d8c47ff..e259a07 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.2.0"), .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.7.0"), - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.2.1"), + .package(url: "https://github.com/apple/swift-collections", from: "1.0.2"), + .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.7.0"), ], targets: [ .target( @@ -28,7 +29,8 @@ let package = Package( "RxSwift", .product(name: "RxRelay", package: "RxSwift"), .product(name: "CasePaths", package: "swift-case-paths"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), + .product(name: "IdentifiedCollections", package: "swift-identified-collections"), + .product(name: "OrderedCollections", package: "swift-collections"), ]), ] ) diff --git a/Sources/ComposableArchitecture/Debugging/ReducerInstrumentation.swift b/Sources/ComposableArchitecture/Debugging/ReducerInstrumentation.swift index 8c2543b..3464add 100644 --- a/Sources/ComposableArchitecture/Debugging/ReducerInstrumentation.swift +++ b/Sources/ComposableArchitecture/Debugging/ReducerInstrumentation.swift @@ -1,110 +1,110 @@ -import Combine -import os.signpost - -@available(iOS 12.0, *) -extension Reducer { - /// Instruments the reducer with - /// [signposts](https://developer.apple.com/documentation/os/logging/recording_performance_data). - /// Each invocation of the reducer will be measured by an interval, and the lifecycle of its - /// effects will be measured with interval and event signposts. - /// - /// To use, build your app for Instruments (⌘I), create a blank instrument, and then use the "+" - /// icon at top right to add the signpost instrument. Start recording your app (red button at top - /// left) and then you should see timing information for every action sent to the store and every - /// effect executed. - /// - /// Effect instrumentation can be particularly useful for inspecting the lifecycle of long-living - /// effects. For example, if you start an effect (e.g. a location manager) in `onAppear` and - /// forget to tear down the effect in `onDisappear`, it will clearly show in Instruments that the - /// effect never completed. - /// - /// - Parameters: - /// - prefix: A string to print at the beginning of the formatted message for the signpost. - /// - log: An `OSLog` to use for signposts. - /// - Returns: A reducer that has been enhanced with instrumentation. - public func signpost( - _ prefix: String = "", - log: OSLog = OSLog( - subsystem: "co.pointfree.composable-architecture", - category: "Reducer Instrumentation" - ) - ) -> Self { - guard log.signpostsEnabled else { return self } - - // NB: Prevent rendering as "N/A" in Instruments - let zeroWidthSpace = "\u{200B}" - - let prefix = prefix.isEmpty ? zeroWidthSpace : "[\(prefix)] " - - return Self { state, action, environment in - var actionOutput: String! - if log.signpostsEnabled { - actionOutput = debugCaseOutput(action) - os_signpost(.begin, log: log, name: "Action", "%s%s", prefix, actionOutput) - } - let effects = self.run(&state, action, environment) - if log.signpostsEnabled { - os_signpost(.end, log: log, name: "Action") - return effects - .effectSignpost(prefix, log: log, actionOutput: actionOutput) - .eraseToEffect() - } - return effects - } - } -} - -@available(iOS 12.0, *) -extension Effect { - func effectSignpost( - _ prefix: String, - log: OSLog, - actionOutput: String - ) -> Self { - let sid = OSSignpostID(log: log) - return self.asObservable().do(afterNext: { value in - os_signpost( - .event, log: log, name: "Effect Output", "%sOutput from %s", prefix, actionOutput) - }, afterError: { _ in - os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sCancelled", prefix) - }, afterCompleted: { - os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sFinished", prefix) - }, onSubscribed: { - os_signpost( - .begin, log: log, name: "Effect", signpostID: sid, "%sStarted from %s", prefix, - actionOutput) - }, onDispose: { - os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sCancelled", prefix) - }) - .eraseToEffect() - } -} - -func debugCaseOutput(_ value: Any) -> String { - func debugCaseOutputHelp(_ value: Any) -> String { - let mirror = Mirror(reflecting: value) - switch mirror.displayStyle { - case .enum: - guard let child = mirror.children.first else { - let childOutput = "\(value)" - return childOutput == "\(type(of: value))" ? "" : ".\(childOutput)" - } - let childOutput = debugCaseOutputHelp(child.value) - return ".\(child.label ?? "")\(childOutput.isEmpty ? "" : "(\(childOutput))")" - case .tuple: - return mirror.children.map { label, value in - let childOutput = debugCaseOutputHelp(value) - return "\(label.map { isUnlabeledArgument($0) ? "_:" : "\($0):" } ?? "")\(childOutput.isEmpty ? "" : " \(childOutput)")" - } - .joined(separator: ", ") - default: - return "" - } - } - - return "\(type(of: value))\(debugCaseOutputHelp(value))" -} - -private func isUnlabeledArgument(_ label: String) -> Bool { - label.firstIndex(where: { $0 != "." && !$0.isNumber }) == nil -} +//import Combine +//import os.signpost +// +//@available(iOS 12.0, *) +//extension Reducer { +// /// Instruments the reducer with +// /// [signposts](https://developer.apple.com/documentation/os/logging/recording_performance_data). +// /// Each invocation of the reducer will be measured by an interval, and the lifecycle of its +// /// effects will be measured with interval and event signposts. +// /// +// /// To use, build your app for Instruments (⌘I), create a blank instrument, and then use the "+" +// /// icon at top right to add the signpost instrument. Start recording your app (red button at top +// /// left) and then you should see timing information for every action sent to the store and every +// /// effect executed. +// /// +// /// Effect instrumentation can be particularly useful for inspecting the lifecycle of long-living +// /// effects. For example, if you start an effect (e.g. a location manager) in `onAppear` and +// /// forget to tear down the effect in `onDisappear`, it will clearly show in Instruments that the +// /// effect never completed. +// /// +// /// - Parameters: +// /// - prefix: A string to print at the beginning of the formatted message for the signpost. +// /// - log: An `OSLog` to use for signposts. +// /// - Returns: A reducer that has been enhanced with instrumentation. +// public func signpost( +// _ prefix: String = "", +// log: OSLog = OSLog( +// subsystem: "co.pointfree.composable-architecture", +// category: "Reducer Instrumentation" +// ) +// ) -> Self { +// guard log.signpostsEnabled else { return self } +// +// // NB: Prevent rendering as "N/A" in Instruments +// let zeroWidthSpace = "\u{200B}" +// +// let prefix = prefix.isEmpty ? zeroWidthSpace : "[\(prefix)] " +// +// return Self { state, action, environment in +// var actionOutput: String! +// if log.signpostsEnabled { +// actionOutput = debugCaseOutput(action) +// os_signpost(.begin, log: log, name: "Action", "%s%s", prefix, actionOutput) +// } +// let effects = self.run(&state, action, environment) +// if log.signpostsEnabled { +// os_signpost(.end, log: log, name: "Action") +// return effects +// .effectSignpost(prefix, log: log, actionOutput: actionOutput) +// .eraseToEffect() +// } +// return effects +// } +// } +//} +// +////@available(iOS 12.0, *) +//extension EffectTask { +// func effectSignpost( +// _ prefix: String, +// log: OSLog, +// actionOutput: String +// ) -> Self { +// let sid = OSSignpostID(log: log) +// return self.asObservable().do(afterNext: { value in +// os_signpost( +// .event, log: log, name: "Effect Output", "%sOutput from %s", prefix, actionOutput) +// }, afterError: { _ in +// os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sCancelled", prefix) +// }, afterCompleted: { +// os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sFinished", prefix) +// }, onSubscribed: { +// os_signpost( +// .begin, log: log, name: "Effect", signpostID: sid, "%sStarted from %s", prefix, +// actionOutput) +// }, onDispose: { +// os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sCancelled", prefix) +// }) +// .eraseToEffect() +// } +//} +// +////func debugCaseOutput(_ value: Any) -> String { +//// func debugCaseOutputHelp(_ value: Any) -> String { +//// let mirror = Mirror(reflecting: value) +//// switch mirror.displayStyle { +//// case .enum: +//// guard let child = mirror.children.first else { +//// let childOutput = "\(value)" +//// return childOutput == "\(type(of: value))" ? "" : ".\(childOutput)" +//// } +//// let childOutput = debugCaseOutputHelp(child.value) +//// return ".\(child.label ?? "")\(childOutput.isEmpty ? "" : "(\(childOutput))")" +//// case .tuple: +//// return mirror.children.map { label, value in +//// let childOutput = debugCaseOutputHelp(value) +//// return "\(label.map { isUnlabeledArgument($0) ? "_:" : "\($0):" } ?? "")\(childOutput.isEmpty ? "" : " \(childOutput)")" +//// } +//// .joined(separator: ", ") +//// default: +//// return "" +//// } +//// } +//// +//// return "\(type(of: value))\(debugCaseOutputHelp(value))" +////} +//// +////private func isUnlabeledArgument(_ label: String) -> Bool { +//// label.firstIndex(where: { $0 != "." && !$0.isNumber }) == nil +////} diff --git a/Sources/ComposableArchitecture/Dependencies/Dependency.swift b/Sources/ComposableArchitecture/Dependencies/Dependency.swift new file mode 100644 index 0000000..7cb34cf --- /dev/null +++ b/Sources/ComposableArchitecture/Dependencies/Dependency.swift @@ -0,0 +1,106 @@ +///// A property wrapper for accessing dependencies. +///// +///// All dependencies are stored in ``DependencyValues`` and one uses this property wrapper to gain +///// access to a particular dependency. Typically it used to provide dependencies to features such as +///// an observable object: +///// +///// ```swift +///// final class FeatureModel: ObservableObject { +///// @Dependency(\.apiClient) var apiClient +///// @Dependency(\.continuousClock) var clock +///// @Dependency(\.uuid) var uuid +///// +///// // ... +///// } +///// ``` +///// +///// Or, if you are using [the Composable Architecture][tca]: +///// +///// ```swift +///// struct Feature: ReducerProtocol { +///// @Dependency(\.apiClient) var apiClient +///// @Dependency(\.continuousClock) var clock +///// @Dependency(\.uuid) var uuid +///// +///// // ... +///// } +///// ``` +///// +///// But it can be used in other situations too, such as a helper function: +///// +///// ```swift +///// func sharedEffect() async throws -> Action { +///// @Dependency(\.apiClient) var apiClient +///// @Dependency(\.continuousClock) var clock +///// +///// // ... +///// } +///// ``` +///// +///// > Warning: There are caveats to using `@Dependency` in this style, especially for applications +///// not built in the Composable Architecture or that are not structured around a "single point of +///// entry" concept. See the articles and for more +///// information. +///// +///// For the complete list of dependency values provided by the library, see the properties of the +///// ``DependencyValues`` structure. +///// +///// [tca]: https://github.com/pointfreeco/swift-composable-architecture +//@propertyWrapper +//public struct Dependency: @unchecked Sendable, _HasInitialValues { +// let initialValues: DependencyValues +// // NB: Key paths do not conform to sendable and are instead diagnosed at the time of forming the +// // literal. +// private let keyPath: KeyPath +// private let file: StaticString +// private let fileID: StaticString +// private let line: UInt +// +// /// Creates a dependency property to read the specified key path. +// /// +// /// Don't call this initializer directly. Instead, declare a property with the `Dependency` +// /// property wrapper, and provide the key path of the dependency value that the property should +// /// reflect: +// /// +// /// ```swift +// /// final class FeatureModel: ObservableObject { +// /// @Dependency(\.date) var date +// /// +// /// // ... +// /// } +// /// ``` +// /// +// /// - Parameter keyPath: A key path to a specific resulting value. +// public init( +// _ keyPath: KeyPath, +// file: StaticString = #file, +// fileID: StaticString = #fileID, +// line: UInt = #line +// ) { +// self.initialValues = DependencyValues._current +// self.keyPath = keyPath +// self.file = file +// self.fileID = fileID +// self.line = line +// } +// +// /// The current value of the dependency property. +// public var wrappedValue: Value { +//#if DEBUG +// var currentDependency = DependencyValues.currentDependency +// currentDependency.file = self.file +// currentDependency.fileID = self.fileID +// currentDependency.line = self.line +// return DependencyValues.$currentDependency.withValue(currentDependency) { +// self.initialValues.merging(DependencyValues._current)[keyPath: self.keyPath] +// } +//#else +// return self.initialValues.merging(DependencyValues._current)[keyPath: self.keyPath] +//#endif +// } +//} +// +//protocol _HasInitialValues { +// var initialValues: DependencyValues { get } +//} +// diff --git a/Sources/ComposableArchitecture/Effect.swift b/Sources/ComposableArchitecture/Effect.swift index d0b2a38..c5338b8 100644 --- a/Sources/ComposableArchitecture/Effect.swift +++ b/Sources/ComposableArchitecture/Effect.swift @@ -1,6 +1,8 @@ import Foundation import RxSwift +public typealias EffectTask = Effect + /// The ``Effect`` type encapsulates a unit of work that can be run in the outside world, and can /// feed data back to the ``Store``. It is the perfect place to do side effects, such as network /// requests, saving/loading from disk, creating timers, interacting with dependencies, and more. diff --git a/Sources/ComposableArchitecture/Internal/Box.swift b/Sources/ComposableArchitecture/Internal/Box.swift new file mode 100644 index 0000000..e3fec0a --- /dev/null +++ b/Sources/ComposableArchitecture/Internal/Box.swift @@ -0,0 +1,12 @@ +final class Box { + var wrappedValue: Wrapped + + init(wrappedValue: Wrapped) { + self.wrappedValue = wrappedValue + } + + var boxedValue: Wrapped { + _read { yield self.wrappedValue } + _modify { yield &self.wrappedValue } + } +} diff --git a/Sources/ComposableArchitecture/Internal/Exports.swift b/Sources/ComposableArchitecture/Internal/Exports.swift index a234fdd..6e6311b 100644 --- a/Sources/ComposableArchitecture/Internal/Exports.swift +++ b/Sources/ComposableArchitecture/Internal/Exports.swift @@ -1,2 +1,3 @@ @_exported import CasePaths +@_exported import IdentifiedCollections @_exported import RxSwift diff --git a/Sources/ComposableArchitecture/Internal/OpenExistential.swift b/Sources/ComposableArchitecture/Internal/OpenExistential.swift new file mode 100644 index 0000000..706d9fb --- /dev/null +++ b/Sources/ComposableArchitecture/Internal/OpenExistential.swift @@ -0,0 +1,42 @@ +#if swift(>=5.7) +// MARK: swift(>=5.7) +// MARK: Equatable + +func _isEqual(_ lhs: Any, _ rhs: Any) -> Bool? { + (lhs as? any Equatable)?.isEqual(other: rhs) +} + +extension Equatable { + fileprivate func isEqual(other: Any) -> Bool { + self == other as? Self + } +} +#else +// MARK: - +// MARK: swift(<5.7) + +private enum Witness {} + +// MARK: Equatable + +func _isEqual(_ lhs: Any, _ rhs: Any) -> Bool? { + func open(_: T.Type) -> Bool? { + (Witness.self as? AnyEquatable.Type)?.isEqual(lhs, rhs) + } + return _openExistential(type(of: lhs), do: open) +} + +private protocol AnyEquatable { + static func isEqual(_ lhs: Any, _ rhs: Any) -> Bool +} + +extension Witness: AnyEquatable where T: Equatable { + fileprivate static func isEqual(_ lhs: Any, _ rhs: Any) -> Bool { + guard + let lhs = lhs as? T, + let rhs = rhs as? T + else { return false } + return lhs == rhs + } +} +#endif diff --git a/Sources/ComposableArchitecture/Internal/RuntimeWarnings.swift b/Sources/ComposableArchitecture/Internal/RuntimeWarnings.swift index ac9bb88..a418a28 100644 --- a/Sources/ComposableArchitecture/Internal/RuntimeWarnings.swift +++ b/Sources/ComposableArchitecture/Internal/RuntimeWarnings.swift @@ -1,26 +1,120 @@ +import Foundation + +@_transparent +@usableFromInline +@inline(__always) +func runtimeWarn( + _ message: @autoclosure () -> String, + category: String? = "ComposableArchitecture", + file: StaticString? = nil, + line: UInt? = nil +) { +#if DEBUG + let message = message() + let category = category ?? "Runtime Warning" + if _XCTIsTesting { + if let file = file, let line = line { +// XCTFail(message, file: file, line: line) + } else { +// XCTFail(message) + } + } else { +#if canImport(os) + os_log( + .fault, + dso: dso, + log: OSLog(subsystem: "com.apple.runtime-issues", category: category), + "%@", + message + ) +#else + fputs("\(formatter.string(from: Date())) [\(category)] \(message)\n", stderr) +#endif + } +#endif +} + #if DEBUG - import os - - // NB: Xcode runtime warnings offer a much better experience than traditional assertions and - // breakpoints, but Apple provides no means of creating custom runtime warnings ourselves. - // To work around this, we hook into SwiftUI's runtime issue delivery mechanism, instead. - // - // Feedback filed: https://gist.github.com/stephencelis/a8d06383ed6ccde3e5ef5d1b3ad52bbc - let rw = ( - dso: { () -> UnsafeMutableRawPointer in - let count = _dyld_image_count() - for i in 0.. UnsafeMutableRawPointer in + let count = _dyld_image_count() + for i in 0.. String { + var name = _typeName(type, qualified: true) + if let index = name.firstIndex(of: ".") { + name.removeSubrange(...index) + } + let sanitizedName = + name + .replacingOccurrences( + of: #"<.+>|\(unknown context at \$[[:xdigit:]]+\)\."#, + with: "", + options: .regularExpression + ) + return sanitizedName +} diff --git a/Sources/ComposableArchitecture/Reducer.swift b/Sources/ComposableArchitecture/Reducer.swift index 34096d2..db5ffab 100644 --- a/Sources/ComposableArchitecture/Reducer.swift +++ b/Sources/ComposableArchitecture/Reducer.swift @@ -467,43 +467,43 @@ public struct Reducer { guard let localAction = toLocalAction.extract(from: globalAction) else { return .none } guard var localState = toLocalState.extract(from: globalState) else { - if #available(iOS 12.0, *) { -#if DEBUG - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - A reducer pulled back from "%@:%d" received an action when local state was \ - unavailable. … - - Action: - %@ - - This is generally considered an application logic error, and can happen for a few \ - reasons: - - • The reducer for a particular case of state was combined with or run from another \ - reducer that set "%@" to another case before the reducer ran. Combine or run \ - case-specific reducers before reducers that may set their state to another case. This \ - ensures that case-specific reducers can handle their actions while their state is \ - available. - - • An in-flight effect emitted this action when state was unavailable. While it may be \ - perfectly reasonable to ignore this action, you may want to cancel the associated \ - effect before state is set to another case, especially if it is a long-living effect. - - • This action was sent to the store while state was another case. Make sure that \ - actions for this reducer can only be sent to a view store when state is non-"nil". \ - In SwiftUI applications, use "SwitchStore". - """, - "\(file)", - line, - debugCaseOutput(localAction), - "\(State.self)" - ) -#endif - } else { - // Fallback on earlier versions - } +// if #available(iOS 12.0, *) { +//#if DEBUG +// os_log( +// .fault, dso: rw.dso, log: rw.log, +// """ +// A reducer pulled back from "%@:%d" received an action when local state was \ +// unavailable. … +// +// Action: +// %@ +// +// This is generally considered an application logic error, and can happen for a few \ +// reasons: +// +// • The reducer for a particular case of state was combined with or run from another \ +// reducer that set "%@" to another case before the reducer ran. Combine or run \ +// case-specific reducers before reducers that may set their state to another case. This \ +// ensures that case-specific reducers can handle their actions while their state is \ +// available. +// +// • An in-flight effect emitted this action when state was unavailable. While it may be \ +// perfectly reasonable to ignore this action, you may want to cancel the associated \ +// effect before state is set to another case, especially if it is a long-living effect. +// +// • This action was sent to the store while state was another case. Make sure that \ +// actions for this reducer can only be sent to a view store when state is non-"nil". \ +// In SwiftUI applications, use "SwitchStore". +// """, +// "\(file)", +// line, +// debugCaseOutput(localAction), +// "\(State.self)" +// ) +//#endif +// } else { +// // Fallback on earlier versions +// } return .none } defer { globalState = toLocalState.embed(localState) } @@ -681,41 +681,41 @@ public struct Reducer { > { .init { state, action, environment in guard state != nil else { - if #available(iOS 12.0, *) { -#if DEBUG - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - An "optional" reducer at "%@:%d" received an action when state was "nil". … - - Action: - %@ - - This is generally considered an application logic error, and can happen for a few \ - reasons: - - • The optional reducer was combined with or run from another reducer that set \ - "%@" to "nil" before the optional reducer ran. Combine or run optional reducers before \ - reducers that can set their state to "nil". This ensures that optional reducers can \ - handle their actions while their state is still non-"nil". - - • An in-flight effect emitted this action while state was "nil". While it may be \ - perfectly reasonable to ignore this action, you may want to cancel the associated \ - effect before state is set to "nil", especially if it is a long-living effect. - - • This action was sent to the store while state was "nil". Make sure that actions for \ - this reducer can only be sent to a view store when state is non-"nil". In SwiftUI \ - applications, use "IfLetStore". - """, - "\(file)", - line, - debugCaseOutput(action), - "\(State.self)" - ) -#endif - } else { - // Fallback on earlier versions - } +// if #available(iOS 12.0, *) { +//#if DEBUG +// os_log( +// .fault, dso: rw.dso, log: rw.log, +// """ +// An "optional" reducer at "%@:%d" received an action when state was "nil". … +// +// Action: +// %@ +// +// This is generally considered an application logic error, and can happen for a few \ +// reasons: +// +// • The optional reducer was combined with or run from another reducer that set \ +// "%@" to "nil" before the optional reducer ran. Combine or run optional reducers before \ +// reducers that can set their state to "nil". This ensures that optional reducers can \ +// handle their actions while their state is still non-"nil". +// +// • An in-flight effect emitted this action while state was "nil". While it may be \ +// perfectly reasonable to ignore this action, you may want to cancel the associated \ +// effect before state is set to "nil", especially if it is a long-living effect. +// +// • This action was sent to the store while state was "nil". Make sure that actions for \ +// this reducer can only be sent to a view store when state is non-"nil". In SwiftUI \ +// applications, use "IfLetStore". +// """, +// "\(file)", +// line, +// debugCaseOutput(action), +// "\(State.self)" +// ) +//#endif +// } else { +// // Fallback on earlier versions +// } return .none } return self.reducer(&state!, action, environment) @@ -804,46 +804,46 @@ public struct Reducer { guard let (key, localAction) = toLocalAction.extract(from: globalAction) else { return .none } if globalState[keyPath: toLocalState][key] == nil { - if #available(iOS 12.0, *) { -#if DEBUG - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - A "forEach" reducer at "%@:%d" received an action when state contained no value at \ - that key. … - - Action: - %@ - Key: - %@ - - This is generally considered an application logic error, and can happen for a few \ - reasons: - - • This "forEach" reducer was combined with or run from another reducer that removed \ - the element at this key when it handled this action. To fix this make sure that this \ - "forEach" reducer is run before any other reducers that can move or remove elements \ - from state. This ensures that "forEach" reducers can handle their actions for the \ - element at the intended key. - - • An in-flight effect emitted this action while state contained no element at this \ - key. It may be perfectly reasonable to ignore this action, but you also may want to \ - cancel the effect it originated from when removing a value from the dictionary, \ - especially if it is a long-living effect. - - • This action was sent to the store while its state contained no element at this \ - key. To fix this make sure that actions for this reducer can only be sent to a view \ - store when its state contains an element at this key. - """, - "\(file)", - line, - debugCaseOutput(localAction), - "\(key)" - ) -#endif - } else { - // Fallback on earlier versions - } +// if #available(iOS 12.0, *) { +//#if DEBUG +// os_log( +// .fault, dso: rw.dso, log: rw.log, +// """ +// A "forEach" reducer at "%@:%d" received an action when state contained no value at \ +// that key. … +// +// Action: +// %@ +// Key: +// %@ +// +// This is generally considered an application logic error, and can happen for a few \ +// reasons: +// +// • This "forEach" reducer was combined with or run from another reducer that removed \ +// the element at this key when it handled this action. To fix this make sure that this \ +// "forEach" reducer is run before any other reducers that can move or remove elements \ +// from state. This ensures that "forEach" reducers can handle their actions for the \ +// element at the intended key. +// +// • An in-flight effect emitted this action while state contained no element at this \ +// key. It may be perfectly reasonable to ignore this action, but you also may want to \ +// cancel the effect it originated from when removing a value from the dictionary, \ +// especially if it is a long-living effect. +// +// • This action was sent to the store while its state contained no element at this \ +// key. To fix this make sure that actions for this reducer can only be sent to a view \ +// store when its state contains an element at this key. +// """, +// "\(file)", +// line, +// debugCaseOutput(localAction), +// "\(key)" +// ) +//#endif +// } else { +// // Fallback on earlier versions +// } return .none } return self.reducer( diff --git a/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducer.swift b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducer.swift new file mode 100644 index 0000000..e79d005 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducer.swift @@ -0,0 +1,1229 @@ +//import CasePaths +//import RxSwift +// +///// This API has been soft-deprecated in favor of ``ReducerProtocol``. +///// Read for more information. +///// +///// A reducer describes how to evolve the current state of an application to the next state, given +///// an action, and describes what ``EffectTask``s should be executed later by the store, if any. +///// +///// Reducers have 3 generics: +///// +///// * `State`: A type that holds the current state of the application. +///// * `Action`: A type that holds all possible actions that cause the state of the application to +///// change. +///// * `Environment`: A type that holds all dependencies needed in order to produce +///// ``EffectTask``s, such as API clients, analytics clients, random number generators, etc. +///// +///// > Important: The thread on which effects output is important. An effect's output is immediately +///// sent back into the store, and ``Store`` is not thread safe. This means all effects must +///// receive values on the same thread, **and** if the ``Store`` is being used to drive UI then all +///// output must be on the main thread. You can use the `Publisher` method `receive(on:)` for make +///// the effect output its values on the thread of your choice. +///// > +///// > This is only an issue if using the Combine interface of ``EffectPublisher`` as mentioned +///// above. If you are only using Swift's concurrency tools and the `.task`, `.run` and +///// `.fireAndForget` functions on ``EffectTask``, then the threading is automatically handled for +///// you. +//@available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +//) +//@available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +//) +//@available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +//) +//@available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +//) +//public struct AnyReducer { +// private let reducer: (inout State, Action, Environment) -> EffectTask +// +// /// > This API has been soft-deprecated in favor of ``ReducerProtocol``. +// /// Read for more information. +// /// +// /// Initializes a reducer from a simple reducer function signature. +// /// +// /// The reducer takes three arguments: state, action and environment. The state is `inout` so that +// /// you can make any changes to it directly inline. The reducer must return an effect, which +// /// typically would be constructed by using the dependencies inside the `environment` value. If +// /// no effect needs to be executed, a ``EffectPublisher/none`` effect can be returned. +// /// +// /// For example: +// /// +// /// ```swift +// /// struct MyState { var count = 0, text = "" } +// /// enum MyAction { case buttonTapped, textChanged(String) } +// /// struct MyEnvironment { var analyticsClient: AnalyticsClient } +// /// +// /// let myReducer = AnyReducer { state, action, environment in +// /// switch action { +// /// case .buttonTapped: +// /// state.count += 1 +// /// return environment.analyticsClient.track("Button Tapped") +// /// +// /// case .textChanged(let text): +// /// state.text = text +// /// return .none +// /// } +// /// } +// /// ``` +// /// +// /// - Parameter reducer: A function signature that takes state, action and +// /// environment. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public init(_ reducer: @escaping (inout State, Action, Environment) -> EffectTask) { +// self.reducer = reducer +// } +// +// /// This API has been soft-deprecated in favor of ``EmptyReducer``. +// /// Read for more information. +// /// +// /// A reducer that performs no state mutations and returns no effects. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'EmptyReducer'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'EmptyReducer'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'EmptyReducer'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'EmptyReducer'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public static var empty: AnyReducer { +// Self { _, _, _ in .none } +// } +// +// /// This API has been soft-deprecated in favor of combining reducers in a ``ReducerBuilder``. Read +// /// for more information. +// /// +// /// Combines many reducers into a single one by running each one on state in order, and merging +// /// all of the effects. +// /// +// /// It is important to note that the order of combining reducers matter. Combining `reducerA` with +// /// `reducerB` is not necessarily the same as combining `reducerB` with `reducerA`. +// /// +// /// This can become an issue when working with reducers that have overlapping domains. For +// /// example, if `reducerA` embeds the domain of `reducerB` and reacts to its actions or modifies +// /// its state, it can make a difference if `reducerA` chooses to modify `reducerB`'s state +// /// _before_ or _after_ `reducerB` runs. +// /// +// /// This is perhaps most easily seen when working with ``optional(file:fileID:line:)`` reducers, +// /// where the parent domain may listen to the child domain and `nil` out its state. If the parent +// /// reducer runs before the child reducer, then the child reducer will not be able to react to its +// /// own action. +// /// +// /// Similar can be said for a ``forEach(state:action:environment:file:fileID:line:)-2ypoa`` +// /// reducer. If the parent domain modifies the child collection by moving, removing, or modifying +// /// an element before the `forEach` reducer runs, the `forEach` reducer may perform its action +// /// against the wrong element, an element that no longer exists, or an element in an unexpected +// /// state. +// /// +// /// Running a parent reducer before a child reducer can be considered an application logic +// /// error, and can produce assertion failures. So you should almost always combine reducers in +// /// order from child to parent domain. +// /// +// /// Here is an example of how you should combine an ``optional(file:fileID:line:)`` reducer with a +// /// parent domain: +// /// +// /// ```swift +// /// let parentReducer = AnyReducer.combine( +// /// // Combined before parent so that it can react to `.dismiss` while state is non-`nil`. +// /// childReducer.optional().pullback( +// /// state: \.child, +// /// action: /ParentAction.child, +// /// environment: { $0.child } +// /// ), +// /// // Combined after child so that it can `nil` out child state upon `.child(.dismiss)`. +// /// AnyReducer { state, action, environment in +// /// switch action +// /// case .child(.dismiss): +// /// state.child = nil +// /// return .none +// /// ... +// /// } +// /// }, +// /// ) +// /// ``` +// /// +// /// - Parameter reducers: A list of reducers. +// /// - Returns: A single reducer. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public static func combine(_ reducers: Self...) -> Self { +// .combine(reducers) +// } +// +// /// This API has been soft-deprecated in favor of combining reducers in a ``ReducerBuilder``. Read +// /// for more information. +// /// +// /// Combines many reducers into a single one by running each one on state in order, and merging +// /// all of the effects. +// /// +// /// This method is identical to ``AnyReducer/combine(_:)-94fzl`` except that it takes an array +// /// of reducers instead of a variadic list. See the documentation on +// /// ``AnyReducer/combine(_:)-94fzl`` for more information about what this method does. +// /// +// /// - Parameter reducers: An array of reducers. +// /// - Returns: A single reducer. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public static func combine(_ reducers: [Self]) -> Self { +// Self { state, action, environment in +// reducers.reduce(.none) { $0.merge(with: $1(&state, action, environment)) } +// } +// } +// +// /// This API has been soft-deprecated in favor of combining reducers in a ``ReducerBuilder``. Read +// /// for more information. +// /// +// /// Combines the receiving reducer with one other reducer, running the second after the first and +// /// merging all of the effects. +// /// +// /// This method is identical to ``AnyReducer/combine(_:)-94fzl`` except that it combines the +// /// receiver with a single other reducer rather than combining a whole list of reducers. See the +// /// documentation on ``AnyReducer/combine(_:)-94fzl`` for more information about what this method +// /// does. +// /// +// /// - Parameter other: Another reducer. +// /// - Returns: A single reducer. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of combining reducers in a 'ReducerBuilder'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func combined(with other: Self) -> Self { +// Self { state, action, environment in +// self(&state, action, environment).merge(with: other(&state, action, environment)) +// } +// } +// +// /// This API has been soft-deprecated in favor of ``Scope``. Read +// /// for more information. +// /// +// /// Transforms a reducer that works on child state, action, and environment into one that works on +// /// parent state, action and environment. It accomplishes this by providing 3 transformations to +// /// the method: +// /// +// /// * A writable key path that can get/set a piece of child state from the parent state. +// /// * A case path that can extract/embed a child action into a parent action. +// /// * A function that can transform the parent environment into a child environment. +// /// +// /// This operation is important for breaking down large reducers into small ones. When used with +// /// the ``combine(_:)-y8ee`` operator you can define many reducers that work on small pieces of +// /// domain, and then _pull them back_ and _combine_ them into one big reducer that works on a +// /// large domain. +// /// +// /// ```swift +// /// // Global domain that holds a child domain: +// /// struct AppState { var settings: SettingsState, /* rest of state */ } +// /// enum AppAction { case settings(SettingsAction), /* other actions */ } +// /// struct AppEnvironment { var settings: SettingsEnvironment, /* rest of dependencies */ } +// /// +// /// // A reducer that works on the child domain: +// /// let settingsReducer = AnyReducer { ... } +// /// +// /// // Pullback the settings reducer so that it works on all of the app domain: +// /// let appReducer = AnyReducer.combine( +// /// settingsReducer.pullback( +// /// state: \.settings, +// /// action: /AppAction.settings, +// /// environment: { $0.settings } +// /// ), +// /// +// /// /* other reducers */ +// /// ) +// /// ``` +// /// +// /// - Parameters: +// /// - toChildState: A key path that can get/set `State` inside `ParentState`. +// /// - toChildAction: A case path that can extract/embed `Action` from `ParentAction`. +// /// - toChildEnvironment: A function that transforms `ParentEnvironment` into `Environment`. +// /// - Returns: A reducer that works on `ParentState`, `ParentAction`, `ParentEnvironment`. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func pullback( +// state toChildState: WritableKeyPath, +// action toChildAction: CasePath, +// environment toChildEnvironment: @escaping (ParentEnvironment) -> Environment +// ) -> AnyReducer { +// .init { parentState, parentAction, parentEnvironment in +// guard let childAction = toChildAction.extract(from: parentAction) else { return .none } +// return self.reducer( +// &parentState[keyPath: toChildState], +// childAction, +// toChildEnvironment(parentEnvironment) +// ) +// .map { toChildAction.embed($0) } +// } +// } +// +// /// This API has been soft-deprecated in favor of +// /// ``ReducerProtocol/ifCaseLet(_:action:then:file:fileID:line:)`` and +// /// ``Scope/init(state:action:child:file:fileID:line:)``. Read +// /// for more information. +// /// +// /// Transforms a reducer that works on child state, action, and environment into one that works on +// /// parent state, action and environment. +// /// +// /// It accomplishes this by providing 3 transformations to the method: +// /// +// /// * A case path that can extract/embed a piece of child state from the parent state, which is +// /// typically an enum. +// /// * A case path that can extract/embed a child action into a parent action. +// /// * A function that can transform the parent environment into a child environment. +// /// +// /// This overload of ``pullback(state:action:environment:)`` differs from the other in that it +// /// takes a `CasePath` transformation for the state instead of a `WritableKeyPath`. This makes it +// /// perfect for working on enum state as opposed to struct state. In particular, you can use this +// /// operator to pullback a reducer that operates on a single case of some state enum to work on +// /// the entire state enum. +// /// +// /// When used with the ``combine(_:)-94fzl`` operator you can define many reducers that work each +// /// case of the state enum, and then _pull them back_ and _combine_ them into one big reducer that +// /// works on a large domain. +// /// +// /// ```swift +// /// // Parent domain that holds a child domain: +// /// enum AppState { case loggedIn(LoggedInState), /* rest of state */ } +// /// enum AppAction { case loggedIn(LoggedInAction), /* other actions */ } +// /// struct AppEnvironment { var loggedIn: LoggedInEnvironment, /* rest of dependencies */ } +// /// +// /// // A reducer that works on the child domain: +// /// let loggedInReducer = AnyReducer { ... } +// /// +// /// // Pullback the logged-in reducer so that it works on all of the app domain: +// /// let appReducer: AnyReducer = .combine( +// /// loggedInReducer.pullback( +// /// state: /AppState.loggedIn, +// /// action: /AppAction.loggedIn, +// /// environment: { $0.loggedIn } +// /// ), +// /// +// /// /* other reducers */ +// /// ) +// /// ``` +// /// +// /// Take care when combining a child reducer for a particular case of enum state into its parent +// /// domain. A child reducer cannot process actions in its domain if it fails to extract its +// /// corresponding state. If a child action is sent to a reducer when its state is unavailable, it +// /// is generally considered a logic error, and a runtime warning will be logged. There are a few +// /// ways in which these errors can sneak into a code base: +// /// +// /// * A parent reducer sets child state to a different case when processing a child action and +// /// runs _before_ the child reducer: +// /// +// /// ```swift +// /// let parentReducer = AnyReducer.combine( +// /// // When combining reducers, the parent reducer runs first +// /// AnyReducer { state, action, environment in +// /// switch action { +// /// case .child(.didDisappear): +// /// // And `nil`s out child state when processing a child action +// /// state.child = .anotherChild(AnotherChildState()) +// /// return .none +// /// ... +// /// } +// /// }, +// /// // Before the child reducer runs +// /// childReducer.pullback(state: /ParentState.child, ...) +// /// ) +// /// +// /// let childReducer = Reducer< +// /// ChildState, ChildAction, ChildEnvironment +// /// > { state, action environment in +// /// case .didDisappear: +// /// // This action is never received here because child state cannot be extracted +// /// ... +// /// } +// /// ``` +// /// +// /// To ensure that a child reducer can process any action that a parent may use to change its +// /// state, combine it _before_ the parent: +// /// +// /// ```swift +// /// let parentReducer = Reducer.combine( +// /// // The child runs first +// /// childReducer.pullback(state: /ParentState.child, ...), +// /// // The parent runs after +// /// Reducer { state, action, environment in +// /// ... +// /// } +// /// ) +// /// ``` +// /// +// /// * A child effect feeds a child action back into the store when child state is unavailable: +// /// +// /// ```swift +// /// let childReducer = Reducer< +// /// ChildState, ChildAction, ChildEnvironment +// /// > { state, action environment in +// /// switch action { +// /// case .onAppear: +// /// // An effect may want to later feed a result back to the child domain in an action +// /// return environment.apiClient +// /// .request() +// /// .map(ChildAction.response) +// /// +// /// case let .response(response): +// /// // But the child cannot process this action if its state is unavailable +// /// ... +// /// } +// /// } +// /// ``` +// /// +// /// It is perfectly reasonable to ignore the result of an effect when child state is `nil`, +// /// for example one-off effects that you don't want to cancel. However, many long-living +// /// effects _should_ be explicitly canceled when tearing down a child domain: +// /// +// /// ```swift +// /// let childReducer = Reducer< +// /// ChildState, ChildAction, ChildEnvironment +// /// > { state, action environment in +// /// enum MotionID {} +// /// +// /// switch action { +// /// case .onAppear: +// /// // Mark long-living effects that shouldn't outlive their domain cancellable +// /// return environment.motionClient +// /// .start() +// /// .map(ChildAction.motion) +// /// .cancellable(id: MotionID.self) +// /// +// /// case .onDisappear: +// /// // And explicitly cancel them when the domain is torn down +// /// return .cancel(id: MotionID.self) +// /// ... +// /// } +// /// } +// /// ``` +// /// +// /// * A view store sends a child action when child state is `nil`: +// /// +// /// ```swift +// /// WithViewStore(self.parentStore) { parentViewStore in +// /// // If child state is `nil`, it cannot process this action. +// /// Button("Child Action") { parentViewStore.send(.child(.action)) } +// /// ... +// /// } +// /// ``` +// /// +// /// Use ``Store/scope(state:action:)`` with ``SwitchStore`` to ensure that views can only send +// /// child actions when the child domain is available. +// /// +// /// ```swift +// /// SwitchStore(self.parentStore) { +// /// CaseLet(state: /ParentState.child, action: ParentAction.child) { childStore in +// /// // This destination only appears when child state matches +// /// WithViewStore(childStore) { childViewStore in +// /// // So this action can only be sent when child state is available +// /// Button("Child Action") { childViewStore.send(.action) } +// /// } +// /// } +// /// ... +// /// } +// /// ``` +// /// +// /// - See also: ``SwitchStore``, a SwiftUI helper for transforming a store on enum state into +// /// stores on each case of the enum. +// /// +// /// - Parameters: +// /// - toChildState: A case path that can extract/embed `State` from `ParentState`. +// /// - toChildAction: A case path that can extract/embed `Action` from `ParentAction`. +// /// - toChildEnvironment: A function that transforms `ParentEnvironment` into `Environment`. +// /// - Returns: A reducer that works on `ParentState`, `ParentAction`, `ParentEnvironment`. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifCaseLet' and 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifCaseLet' and 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifCaseLet' and 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifCaseLet' and 'Scope'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func pullback( +// state toChildState: CasePath, +// action toChildAction: CasePath, +// environment toChildEnvironment: @escaping (ParentEnvironment) -> Environment, +// file: StaticString = #file, +// fileID: StaticString = #fileID, +// line: UInt = #line +// ) -> AnyReducer { +// .init { parentState, parentAction, parentEnvironment in +// guard let childAction = toChildAction.extract(from: parentAction) else { return .none } +// +// guard var childState = toChildState.extract(from: parentState) else { +// runtimeWarn( +// """ +// A reducer pulled back from "\(fileID):\(line)" received an action when child state was \ +// unavailable. … +// +// Action: +// \(debugCaseOutput(childAction)) +// +// This is generally considered an application logic error, and can happen for a few \ +// reasons: +// +// • The reducer for a particular case of state was combined with or run from another \ +// reducer that set "\(typeName(State.self))" to another case before the reducer ran. \ +// Combine or run case-specific reducers before reducers that may set their state to \ +// another case. This ensures that case-specific reducers can handle their actions while \ +// their state is available. +// +// • An in-flight effect emitted this action when state was unavailable. While it may be \ +// perfectly reasonable to ignore this action, you may want to cancel the associated \ +// effect before state is set to another case, especially if it is a long-living effect. +// +// • This action was sent to the store while state was another case. Make sure that \ +// actions for this reducer can only be sent to a view store when state is non-"nil". \ +// In SwiftUI applications, use "SwitchStore". +// """, +// file: file, +// line: line +// ) +// return .none +// } +// defer { parentState = toChildState.embed(childState) } +// +// let effects = self.run( +// &childState, +// childAction, +// toChildEnvironment(parentEnvironment) +// ) +// .map { toChildAction.embed($0) } +// +// return effects +// } +// } +// +// /// This API has been soft-deprecated in favor of +// /// ``ReducerProtocol/ifLet(_:action:then:file:fileID:line:)``. Read +// /// for more information. +// /// +// /// Transforms a reducer that works on non-optional state into one that works on optional state by +// /// only running the non-optional reducer when state is non-nil. +// /// +// /// Often used in tandem with ``pullback(state:action:environment:)`` to transform a reducer on a +// /// non-optional child domain into a reducer that can be combined with a reducer on a parent +// /// domain that contains some optional child domain: +// /// +// /// ```swift +// /// // Parent domain that holds an optional child domain: +// /// struct AppState { var modal: ModalState? } +// /// enum AppAction { case modal(ModalAction) } +// /// struct AppEnvironment { var mainQueue: AnySchedulerOf } +// /// +// /// // A reducer that works on the non-optional child domain: +// /// let modalReducer = Reducer.combine( +// /// modalReducer.optional().pullback( +// /// state: \.modal, +// /// action: /AppAction.modal, +// /// environment: { ModalEnvironment(mainQueue: $0.mainQueue) } +// /// ), +// /// Reducer { state, action, environment in +// /// ... +// /// } +// /// ) +// /// ``` +// /// +// /// Take care when combining optional reducers into parent domains. An optional reducer cannot +// /// process actions in its domain when its state is `nil`. If a child action is sent to an +// /// optional reducer when child state is `nil`, it is generally considered a logic error. There +// /// are a few ways in which these errors can sneak into a code base: +// /// +// /// * A parent reducer sets child state to `nil` when processing a child action and runs +// /// _before_ the child reducer: +// /// +// /// ```swift +// /// let parentReducer = Reducer.combine( +// /// // When combining reducers, the parent reducer runs first +// /// Reducer { state, action, environment in +// /// switch action { +// /// case .child(.didDisappear): +// /// // And `nil`s out child state when processing a child action +// /// state.child = nil +// /// return .none +// /// ... +// /// } +// /// }, +// /// // Before the child reducer runs +// /// childReducer.optional().pullback(...) +// /// ) +// /// +// /// let childReducer = Reducer< +// /// ChildState, ChildAction, ChildEnvironment +// /// > { state, action environment in +// /// case .didDisappear: +// /// // This action is never received here because child state is `nil` in the parent +// /// ... +// /// } +// /// ``` +// /// +// /// To ensure that a child reducer can process any action that a parent may use to `nil` out +// /// its state, combine it _before_ the parent: +// /// +// /// ```swift +// /// let parentReducer = Reducer.combine( +// /// // The child runs first +// /// childReducer.optional().pullback(...), +// /// // The parent runs after +// /// Reducer { state, action, environment in +// /// ... +// /// } +// /// ) +// /// ``` +// /// +// /// * A child effect feeds a child action back into the store when child state is `nil`: +// /// +// /// ```swift +// /// let childReducer = Reducer< +// /// ChildState, ChildAction, ChildEnvironment +// /// > { state, action environment in +// /// switch action { +// /// case .onAppear: +// /// // An effect may want to feed its result back to the child domain in an action +// /// return environment.apiClient +// /// .request() +// /// .map(ChildAction.response) +// /// +// /// case let .response(response): +// /// // But the child cannot process this action if its state is `nil` in the parent +// /// ... +// /// } +// /// } +// /// ``` +// /// +// /// It is perfectly reasonable to ignore the result of an effect when child state is `nil`, +// /// for example one-off effects that you don't want to cancel. However, many long-living +// /// effects _should_ be explicitly canceled when tearing down a child domain: +// /// +// /// ```swift +// /// let childReducer = Reducer< +// /// ChildState, ChildAction, ChildEnvironment +// /// > { state, action environment in +// /// enum MotionID {} +// /// +// /// switch action { +// /// case .onAppear: +// /// // Mark long-living effects that shouldn't outlive their domain cancellable +// /// return environment.motionClient +// /// .start() +// /// .map(ChildAction.motion) +// /// .cancellable(id: MotionID.self) +// /// +// /// case .onDisappear: +// /// // And explicitly cancel them when the domain is torn down +// /// return .cancel(id: MotionID.self) +// /// ... +// /// } +// /// } +// /// ``` +// /// +// /// * A view store sends a child action when child state is `nil`: +// /// +// /// ```swift +// /// WithViewStore(self.parentStore) { parentViewStore in +// /// // If child state is `nil`, it cannot process this action. +// /// Button("Child Action") { parentViewStore.send(.child(.action)) } +// /// ... +// /// } +// /// ``` +// /// +// /// Use ``Store/scope(state:action:)`` with ``IfLetStore`` or ``Store/ifLet(then:else:)`` to +// /// ensure that views can only send child actions when the child domain is non-`nil`. +// /// +// /// ```swift +// /// IfLetStore( +// /// self.parentStore.scope(state: { $0.child }, action: { .child($0) } +// /// ) { childStore in +// /// // This destination only appears when child state is non-`nil` +// /// WithViewStore(childStore) { childViewStore in +// /// // So this action can only be sent when child state is non-`nil` +// /// Button("Child Action") { childViewStore.send(.action) } +// /// } +// /// ... +// /// } +// /// ``` +// /// +// /// - See also: ``IfLetStore``, a SwiftUI helper for transforming a store on optional state into a +// /// store on non-optional state. +// /// - See also: ``Store/ifLet(then:else:)``, a UIKit helper for doing imperative work with a store +// /// on optional state. +// /// +// /// - Returns: A reducer that works on optional state. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifLet'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifLet'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifLet'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.ifLet'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func optional( +// file: StaticString = #file, +// fileID: StaticString = #fileID, +// line: UInt = #line +// ) -> AnyReducer< +// State?, Action, Environment +// > { +// .init { state, action, environment in +// guard state != nil else { +// runtimeWarn( +// """ +// An "optional" reducer at "\(fileID):\(line)" received an action when state was "nil". … +// +// Action: +// \(debugCaseOutput(action)) +// +// This is generally considered an application logic error, and can happen for a few \ +// reasons: +// +// • The optional reducer was combined with or run from another reducer that set \ +// "\(typeName(State.self))" to "nil" before the optional reducer ran. Combine or run \ +// optional reducers before reducers that can set their state to "nil". This ensures that \ +// optional reducers can handle their actions while their state is still non-"nil". +// +// • An in-flight effect emitted this action while state was "nil". While it may be \ +// perfectly reasonable to ignore this action, you may want to cancel the associated \ +// effect before state is set to "nil", especially if it is a long-living effect. +// +// • This action was sent to the store while state was "nil". Make sure that actions for \ +// this reducer can only be sent to a view store when state is non-"nil". In SwiftUI \ +// applications, use "IfLetStore". +// """, +// file: file, +// line: line +// ) +// return .none +// } +// return self.reducer(&state!, action, environment) +// } +// } +// +// /// This API has been soft-deprecated in favor of +// /// ``ReducerProtocol/forEach(_:action:element:file:fileID:line:)``. Read +// /// for more information. +// /// +// /// A version of ``pullback(state:action:environment:)`` that transforms a reducer that works on +// /// an element into one that works on an identified array of elements. +// /// +// /// ```swift +// /// // Parent domain that holds a collection of child domains: +// /// struct AppState { var todos: IdentifiedArrayOf } +// /// enum AppAction { case todo(id: Todo.ID, action: TodoAction) } +// /// struct AppEnvironment { var mainQueue: AnySchedulerOf } +// /// +// /// // A reducer that works on an element's domain: +// /// let todoReducer = Reducer { ... } +// /// +// /// // Pullback the todo reducer so that it works on all of the app domain: +// /// let appReducer = Reducer.combine( +// /// todoReducer.forEach( +// /// state: \.todos, +// /// action: /AppAction.todo(id:action:), +// /// environment: { _ in TodoEnvironment() } +// /// ), +// /// Reducer { state, action, environment in +// /// ... +// /// } +// /// ) +// /// ``` +// /// +// /// Take care when combining `forEach` reducers into parent domains, as order matters. Always +// /// combine `forEach` reducers _before_ parent reducers that can modify the collection. +// /// +// /// - Parameters: +// /// - toElementsState: A key path that can get/set a collection of `State` elements inside +// /// `ParentState`. +// /// - toElementAction: A case path that can extract/embed `(ID, Action)` from `ParentAction`. +// /// - toElementEnvironment: A function that transforms `ParentEnvironment` into `Environment`. +// /// - Returns: A reducer that works on `ParentState`, `ParentAction`, `ParentEnvironment`. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.forEach'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.forEach'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.forEach'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.forEach'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func forEach( +// state toElementsState: WritableKeyPath>, +// action toElementAction: CasePath, +// environment toElementEnvironment: @escaping (ParentEnvironment) -> Environment, +// file: StaticString = #file, +// fileID: StaticString = #fileID, +// line: UInt = #line +// ) -> AnyReducer { +// .init { parentState, parentAction, parentEnvironment in +// guard let (id, action) = toElementAction.extract(from: parentAction) +// else { return .none } +// +// if parentState[keyPath: toElementsState][id: id] == nil { +// runtimeWarn( +// """ +// A "forEach" reducer at "\(fileID):\(line)" received an action when state contained no \ +// element with that id. … +// +// Action: +// \(debugCaseOutput(action)) +// ID: +// \(id) +// +// This is generally considered an application logic error, and can happen for a few \ +// reasons: +// +// • This "forEach" reducer was combined with or run from another reducer that removed \ +// the element at this id when it handled this action. To fix this make sure that this \ +// "forEach" reducer is run before any other reducers that can move or remove elements \ +// from state. This ensures that "forEach" reducers can handle their actions for the \ +// element at the intended id. +// +// • An in-flight effect emitted this action while state contained no element at this id. \ +// It may be perfectly reasonable to ignore this action, but you also may want to cancel \ +// the effect it originated from when removing an element from the identified array, \ +// especially if it is a long-living effect. +// +// • This action was sent to the store while its state contained no element at this id. \ +// To fix this make sure that actions for this reducer can only be sent to a view store \ +// when its state contains an element at this id. In SwiftUI applications, use \ +// "ForEachStore". +// """, +// file: file, +// line: line +// ) +// return .none +// } +// return +// self +// .reducer( +// &parentState[keyPath: toElementsState][id: id]!, +// action, +// toElementEnvironment(parentEnvironment) +// ) +// .map { toElementAction.embed((id, $0)) } +// } +// } +// +// /// A version of ``pullback(state:action:environment:)`` that transforms a reducer that works on +// /// an element into one that works on a dictionary of element values. +// /// +// /// Take care when combining `forEach` reducers into parent domains, as order matters. Always +// /// combine `forEach`` reducers _before_ parent reducers that can modify the dictionary. +// /// +// /// - Parameters: +// /// - toDictionaryState: A key path that can get/set a dictionary of `State` values inside +// /// `ParentState`. +// /// - toKeyedAction: A case path that can extract/embed `(Key, Action)` from `ParentAction`. +// /// - toValueEnvironment: A function that transforms `ParentEnvironment` into `Environment`. +// /// - Returns: A reducer that works on `ParentState`, `ParentAction`, `ParentEnvironment`. +// public func forEach( +// state toDictionaryState: WritableKeyPath, +// action toKeyedAction: CasePath, +// environment toValueEnvironment: @escaping (ParentEnvironment) -> Environment, +// file: StaticString = #file, +// fileID: StaticString = #fileID, +// line: UInt = #line +// ) -> AnyReducer { +// .init { parentState, parentAction, parentEnvironment in +// guard let (key, action) = toKeyedAction.extract(from: parentAction) else { return .none } +// +// if parentState[keyPath: toDictionaryState][key] == nil { +// runtimeWarn( +// """ +// A "forEach" reducer at "\(fileID):\(line)" received an action when state contained no \ +// value at that key. … +// +// Action: +// \(debugCaseOutput(action)) +// Key: +// \(key) +// +// This is generally considered an application logic error, and can happen for a few \ +// reasons: +// +// • This "forEach" reducer was combined with or run from another reducer that removed \ +// the element at this key when it handled this action. To fix this make sure that this \ +// "forEach" reducer is run before any other reducers that can move or remove elements \ +// from state. This ensures that "forEach" reducers can handle their actions for the \ +// element at the intended key. +// +// • An in-flight effect emitted this action while state contained no element at this \ +// key. It may be perfectly reasonable to ignore this action, but you also may want to \ +// cancel the effect it originated from when removing a value from the dictionary, \ +// especially if it is a long-living effect. +// +// • This action was sent to the store while its state contained no element at this \ +// key. To fix this make sure that actions for this reducer can only be sent to a view \ +// store when its state contains an element at this key. +// """, +// file: file, +// line: line +// ) +// return .none +// } +// return self.reducer( +// &parentState[keyPath: toDictionaryState][key]!, +// action, +// toValueEnvironment(parentEnvironment) +// ) +// .map { toKeyedAction.embed((key, $0)) } +// } +// } +// +// /// This API has been soft-deprecated in favor of ``ReducerProtocol/reduce(into:action:)-8yinq``. +// /// Read for more information. +// /// +// /// Runs the reducer. +// /// +// /// - Parameters: +// /// - state: Mutable state. +// /// - action: An action. +// /// - environment: An environment. +// /// - Returns: An effect that can emit zero or more actions. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func run( +// _ state: inout State, +// _ action: Action, +// _ environment: Environment +// ) -> EffectTask { +// self.reducer(&state, action, environment) +// } +// +// /// This API has been soft-deprecated in favor of ``ReducerProtocol/reduce(into:action:)-8yinq``. +// /// Read for more information. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.reduce(into:action:)'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func callAsFunction( +// _ state: inout State, +// _ action: Action, +// _ environment: Environment +// ) -> EffectTask { +// self.reducer(&state, action, environment) +// } +//} diff --git a/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerBinding.swift b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerBinding.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerBinding.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerCompatibility.swift b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerCompatibility.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerCompatibility.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerDebug.swift b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerDebug.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerDebug.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerSignpost.swift b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerSignpost.swift new file mode 100644 index 0000000..148500f --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/AnyReducer/AnyReducerSignpost.swift @@ -0,0 +1,144 @@ +import Combine +import os.signpost + +//extension AnyReducer { +// /// Instruments the reducer with +// /// [signposts](https://developer.apple.com/documentation/os/logging/recording_performance_data). +// /// Each invocation of the reducer will be measured by an interval, and the lifecycle of its +// /// effects will be measured with interval and event signposts. +// /// +// /// To use, build your app for Instruments (⌘I), create a blank instrument, and then use the "+" +// /// icon at top right to add the signpost instrument. Start recording your app (red button at top +// /// left) and then you should see timing information for every action sent to the store and every +// /// effect executed. +// /// +// /// Effect instrumentation can be particularly useful for inspecting the lifecycle of long-living +// /// effects. For example, if you start an effect (e.g. a location manager) in `onAppear` and +// /// forget to tear down the effect in `onDisappear`, it will clearly show in Instruments that the +// /// effect never completed. +// /// +// /// - Parameters: +// /// - prefix: A string to print at the beginning of the formatted message for the signpost. +// /// - log: An `OSLog` to use for signposts. +// /// - Returns: A reducer that has been enhanced with instrumentation. +// @available( +// iOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.signpost'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// macOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.signpost'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// tvOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.signpost'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// @available( +// watchOS, +// deprecated: 9999.0, +// message: +// """ +// This API has been soft-deprecated in favor of 'ReducerProtocol.signpost'. Read the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol +// """ +// ) +// public func signpost( +// _ prefix: String = "", +// log: OSLog = OSLog( +// subsystem: "co.pointfree.composable-architecture", +// category: "Reducer Instrumentation" +// ) +// ) -> Self { +// guard log.signpostsEnabled else { return self } +// +// // NB: Prevent rendering as "N/A" in Instruments +// let zeroWidthSpace = "\u{200B}" +// +// let prefix = prefix.isEmpty ? zeroWidthSpace : "[\(prefix)] " +// +// return Self { state, action, environment in +// var actionOutput: String! +// if log.signpostsEnabled { +// actionOutput = debugCaseOutput(action) +// os_signpost(.begin, log: log, name: "Action", "%s%s", prefix, actionOutput) +// } +// let effects = self.run(&state, action, environment) +// if log.signpostsEnabled { +// os_signpost(.end, log: log, name: "Action") +// return +// effects +// .effectSignpost(prefix, log: log, actionOutput: actionOutput) +// } +// return effects +// } +// } +//} + +extension EffectTask { + func effectSignpost( + _ prefix: String, + log: OSLog, + actionOutput: String + ) -> Self { + let sid = OSSignpostID(log: log) + return asObservable().do( + afterNext: { value in + os_signpost( + .event, log: log, name: "Effect Output", "%sOutput from %s", prefix, actionOutput) + }, afterError: { _ in + os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sCancelled", prefix) + }, afterCompleted: { + os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sFinished", prefix) + }, onSubscribed: { + os_signpost( + .begin, log: log, name: "Effect", signpostID: sid, "%sStarted from %s", prefix, + actionOutput) + }, onDispose: { + os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sCancelled", prefix) + } + ) + .eraseToEffect() + } +} + +@usableFromInline +func debugCaseOutput(_ value: Any) -> String { + func debugCaseOutputHelp(_ value: Any) -> String { + let mirror = Mirror(reflecting: value) + switch mirror.displayStyle { + case .enum: + guard let child = mirror.children.first else { + let childOutput = "\(value)" + return childOutput == "\(type(of: value))" ? "" : ".\(childOutput)" + } + let childOutput = debugCaseOutputHelp(child.value) + return ".\(child.label ?? "")\(childOutput.isEmpty ? "" : "(\(childOutput))")" + case .tuple: + return mirror.children.map { label, value in + let childOutput = debugCaseOutputHelp(value) + return "\(label.map { isUnlabeledArgument($0) ? "_:" : "\($0):" } ?? "")\(childOutput.isEmpty ? "" : " \(childOutput)")" + } + .joined(separator: ", ") + default: + return "" + } + } + + return (value as? CustomDebugStringConvertible)?.debugDescription + ?? "\(typeName(type(of: value)))\(debugCaseOutputHelp(value))" +} + +private func isUnlabeledArgument(_ label: String) -> Bool { + label.firstIndex(where: { $0 != "." && !$0.isNumber }) == nil +} diff --git a/Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift b/Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift new file mode 100644 index 0000000..29f1960 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/ReducerBuilder.swift @@ -0,0 +1,381 @@ +/// A result builder for combining reducers into a single reducer by running each, one after the +/// other, and returning their merged effects. +/// +/// It is most common to encounter a reducer builder context when conforming a type to +/// ``ReducerProtocol`` and implementing its ``ReducerProtocol/body-swift.property-97ymy`` property. +/// +/// See ``CombineReducers`` for an entry point into a reducer builder context. +@resultBuilder +public enum ReducerBuilder { + @inlinable + public static func buildArray(_ reducers: [R]) -> _SequenceMany + where R.State == State, R.Action == Action { + _SequenceMany(reducers: reducers) + } + + @inlinable + public static func buildBlock() -> EmptyReducer { + EmptyReducer() + } + + @inlinable + public static func buildBlock(_ reducer: R) -> R + where R.State == State, R.Action == Action { + reducer + } + + @inlinable + public static func buildEither( + first reducer: R0 + ) -> _Conditional + where R0.State == State, R0.Action == Action, R1.State == State, R1.Action == Action { + .first(reducer) + } + + @inlinable + public static func buildEither( + second reducer: R1 + ) -> _Conditional + where R0.State == State, R0.Action == Action, R1.State == State, R1.Action == Action { + .second(reducer) + } + + @inlinable + public static func buildExpression(_ expression: R) -> R + where R.State == State, R.Action == Action { + expression + } + + @inlinable + public static func buildFinalResult(_ reducer: R) -> R + where R.State == State, R.Action == Action { + reducer + } + + @inlinable + public static func buildLimitedAvailability( + _ wrapped: R + ) -> Reduce + where R.State == State, R.Action == Action { + Reduce(wrapped) + } + + @inlinable + public static func buildOptional(_ wrapped: R?) -> R? + where R.State == State, R.Action == Action { + wrapped + } + + @inlinable + public static func buildPartialBlock( + first: R + ) -> R + where R.State == State, R.Action == Action { + first + } + + @inlinable + public static func buildPartialBlock( + accumulated: R0, next: R1 + ) -> _Sequence + where R0.State == State, R0.Action == Action, R1.State == State, R1.Action == Action { + _Sequence(accumulated, next) + } + +#if swift(<5.7) + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol + >( + _ r0: R0, + _ r1: R1 + ) -> _Sequence + where R0.State == State, R0.Action == Action { + _Sequence(r0, r1) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2 + ) -> _Sequence<_Sequence, R2> + where R0.State == State, R0.Action == Action { + _Sequence(_Sequence(r0, r1), r2) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol, + R3: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2, + _ r3: R3 + ) -> _Sequence<_Sequence<_Sequence, R2>, R3> + where R0.State == State, R0.Action == Action { + _Sequence(_Sequence(_Sequence(r0, r1), r2), r3) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol, + R3: ReducerProtocol, + R4: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2, + _ r3: R3, + _ r4: R4 + ) -> _Sequence<_Sequence<_Sequence<_Sequence, R2>, R3>, R4> + where R0.State == State, R0.Action == Action { + _Sequence(_Sequence(_Sequence(_Sequence(r0, r1), r2), r3), r4) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol, + R3: ReducerProtocol, + R4: ReducerProtocol, + R5: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2, + _ r3: R3, + _ r4: R4, + _ r5: R5 + ) -> _Sequence<_Sequence<_Sequence<_Sequence<_Sequence, R2>, R3>, R4>, R5> + where R0.State == State, R0.Action == Action { + _Sequence(_Sequence(_Sequence(_Sequence(_Sequence(r0, r1), r2), r3), r4), r5) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol, + R3: ReducerProtocol, + R4: ReducerProtocol, + R5: ReducerProtocol, + R6: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2, + _ r3: R3, + _ r4: R4, + _ r5: R5, + _ r6: R6 + ) -> _Sequence< + _Sequence<_Sequence<_Sequence<_Sequence<_Sequence, R2>, R3>, R4>, R5>, R6 + > + where R0.State == State, R0.Action == Action { + _Sequence(_Sequence(_Sequence(_Sequence(_Sequence(_Sequence(r0, r1), r2), r3), r4), r5), r6) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol, + R3: ReducerProtocol, + R4: ReducerProtocol, + R5: ReducerProtocol, + R6: ReducerProtocol, + R7: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2, + _ r3: R3, + _ r4: R4, + _ r5: R5, + _ r6: R6, + _ r7: R7 + ) -> _Sequence< + _Sequence<_Sequence<_Sequence<_Sequence<_Sequence<_Sequence, R2>, R3>, R4>, R5>, R6>, + R7 + > + where R0.State == State, R0.Action == Action { + _Sequence( + _Sequence( + _Sequence(_Sequence(_Sequence(_Sequence(_Sequence(r0, r1), r2), r3), r4), r5), r6 + ), + r7 + ) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol, + R3: ReducerProtocol, + R4: ReducerProtocol, + R5: ReducerProtocol, + R6: ReducerProtocol, + R7: ReducerProtocol, + R8: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2, + _ r3: R3, + _ r4: R4, + _ r5: R5, + _ r6: R6, + _ r7: R7, + _ r8: R8 + ) -> _Sequence< + _Sequence< + _Sequence< + _Sequence<_Sequence<_Sequence<_Sequence<_Sequence, R2>, R3>, R4>, R5>, R6 + >, + R7 + >, + R8 + > + where R0.State == State, R0.Action == Action { + _Sequence( + _Sequence( + _Sequence( + _Sequence(_Sequence(_Sequence(_Sequence(_Sequence(r0, r1), r2), r3), r4), r5), r6 + ), + r7 + ), + r8 + ) + } + + @inlinable + public static func buildBlock< + R0: ReducerProtocol, + R1: ReducerProtocol, + R2: ReducerProtocol, + R3: ReducerProtocol, + R4: ReducerProtocol, + R5: ReducerProtocol, + R6: ReducerProtocol, + R7: ReducerProtocol, + R8: ReducerProtocol, + R9: ReducerProtocol + >( + _ r0: R0, + _ r1: R1, + _ r2: R2, + _ r3: R3, + _ r4: R4, + _ r5: R5, + _ r6: R6, + _ r7: R7, + _ r8: R8, + _ r9: R9 + ) -> _Sequence< + _Sequence< + _Sequence< + _Sequence< + _Sequence<_Sequence<_Sequence<_Sequence<_Sequence, R2>, R3>, R4>, R5>, R6 + >, + R7 + >, + R8 + >, + R9 + > + where R0.State == State, R0.Action == Action { + _Sequence( + _Sequence( + _Sequence( + _Sequence( + _Sequence(_Sequence(_Sequence(_Sequence(_Sequence(r0, r1), r2), r3), r4), r5), r6 + ), + r7 + ), + r8 + ), + r9 + ) + } + + @_disfavoredOverload + @inlinable + public static func buildFinalResult(_ reducer: R) -> Reduce + where R.State == State, R.Action == Action { + Reduce(reducer) + } +#endif + + public enum _Conditional: ReducerProtocol + where + First.State == Second.State, + First.Action == Second.Action + { + case first(First) + case second(Second) + + @inlinable + public func reduce(into state: inout First.State, action: First.Action) -> EffectTask< + First.Action + > { + switch self { + case let .first(first): + return first.reduce(into: &state, action: action) + + case let .second(second): + return second.reduce(into: &state, action: action) + } + } + } + + public struct _Sequence: ReducerProtocol + where R0.State == R1.State, R0.Action == R1.Action { + @usableFromInline + let r0: R0 + + @usableFromInline + let r1: R1 + + @usableFromInline + init(_ r0: R0, _ r1: R1) { + self.r0 = r0 + self.r1 = r1 + } + + @inlinable + public func reduce(into state: inout R0.State, action: R0.Action) -> EffectTask { + EffectTask.merge(self.r0.reduce(into: &state, action: action), self.r1.reduce(into: &state, action: action)) + } + } + + public struct _SequenceMany: ReducerProtocol { + @usableFromInline + let reducers: [Element] + + @usableFromInline + init(reducers: [Element]) { + self.reducers = reducers + } + + @inlinable + public func reduce( + into state: inout Element.State, action: Element.Action + ) -> EffectTask { + return .none + // self.reducers.reduce(.none) { $0.merge(with: $1.reduce(into: &state, action: action)) } + } + } +} diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/BindingReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/BindingReducer.swift new file mode 100644 index 0000000..42242a3 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/BindingReducer.swift @@ -0,0 +1,25 @@ +//import SwiftUI +// +///// A reducer that updates bindable state when it receives binding actions. +//public struct BindingReducer: ReducerProtocol +//where Action: BindableAction, State == Action.State { +// /// Initializes a reducer that updates bindable state when it receives binding actions. +// @inlinable +// public init() { +// self.init(internal: ()) +// } +// +// @usableFromInline +// init(internal: Void) {} +// +// @inlinable +// public func reduce( +// into state: inout State, action: Action +// ) -> EffectTask { +// guard let bindingAction = (/Action.binding).extract(from: action) +// else { return .none } +// +// bindingAction.set(&state) +// return .none +// } +//} diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/CombineReducers.swift b/Sources/ComposableArchitecture/Reducer/Reducers/CombineReducers.swift new file mode 100644 index 0000000..f1e2fa4 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/CombineReducers.swift @@ -0,0 +1,45 @@ +/// Combines multiple reducers into a single reducer. +/// +/// `CombineReducers` takes a block that can combine a number of reducers using a +/// ``ReducerBuilder``. +/// +/// Useful for grouping reducers together and applying reducer modifiers to the result. +/// +/// ```swift +/// var body: some ReducerProtocol { +/// CombineReducers { +/// ReducerA() +/// ReducerB() +/// ReducerC() +/// } +/// .ifLet(\.child, action: /Action.child) +/// } +/// ``` +public struct CombineReducers: ReducerProtocol +where State == Reducers.State, Action == Reducers.Action { + @usableFromInline + let reducers: Reducers + + /// Initializes a reducer that combines all of the reducers in the given build block. + /// + /// - Parameter build: A reducer builder. + @inlinable + public init( + @ReducerBuilder _ build: () -> Reducers + ) { + self.init(internal: build()) + } + + @usableFromInline + init(internal reducers: Reducers) { + self.reducers = reducers + } + + @inlinable + public func reduce( + into state: inout Reducers.State, action: Reducers.Action + ) -> EffectTask { + self.reducers.reduce(into: &state, action: action) + } +} + diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/DebugReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/DebugReducer.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/DebugReducer.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/DependencyKeyWritingReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/DependencyKeyWritingReducer.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/DependencyKeyWritingReducer.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/EmptyReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/EmptyReducer.swift new file mode 100644 index 0000000..3ba8145 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/EmptyReducer.swift @@ -0,0 +1,19 @@ +/// A reducer that does nothing. +/// +/// While not very useful on its own, `EmptyReducer` can be used as a placeholder in APIs that hold +/// reducers. +public struct EmptyReducer: ReducerProtocol { + /// Initializes a reducer that does nothing. + @inlinable + public init() { + self.init(internal: ()) + } + + @usableFromInline + init(internal: Void) {} + + @inlinable + public func reduce(into _: inout State, action _: Action) -> EffectTask { + .none + } +} diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift new file mode 100644 index 0000000..eb46920 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift @@ -0,0 +1,164 @@ +extension ReducerProtocol { + /// Embeds a child reducer in a parent domain that works on elements of a collection in parent + /// state. + /// + /// For example, if a parent feature holds onto an array of child states, then it can perform + /// its core logic _and_ the child's logic by using the `forEach` operator: + /// + /// ```swift + /// struct Parent: ReducerProtocol { + /// struct State { + /// var rows: IdentifiedArrayOf + /// // ... + /// } + /// enum Action { + /// case row(id: Row.State.ID, action: Row.Action) + /// // ... + /// } + /// + /// var body: some ReducerProtocol { + /// Reduce { state, action in + /// // Core logic for parent feature + /// } + /// .forEach(\.rows, action: /Action.row) { + /// Row() + /// } + /// } + /// } + /// ``` + /// + /// > Tip: We are using `IdentifiedArray` from our + /// [Identified Collections][swift-identified-collections] library because it provides a safe + /// and ergonomic API for accessing elements from a stable ID rather than positional indices. + /// + /// The `forEach` forces a specific order of operations for the child and parent features. It + /// runs the child first, and then the parent. If the order was reversed, then it would be + /// possible for the parent feature to remove the child state from the array, in which case the + /// child feature would not be able to react to that action. That can cause subtle bugs. + /// + /// It is still possible for a parent feature higher up in the application to remove the child + /// state from the array before the child has a chance to react to the action. In such cases a + /// runtime warning is shown in Xcode to let you know that there's a potential problem. + /// + /// [swift-identified-collections]: http://github.com/pointfreeco/swift-identified-collections + /// + /// - Parameters: + /// - toElementsState: A writable key path from parent state to an `IdentifiedArray` of child + /// state. + /// - toElementAction: A case path from parent action to child identifier and child actions. + /// - element: A reducer that will be invoked with child actions against elements of child + /// state. + /// - Returns: A reducer that combines the child reducer with the parent reducer. + @inlinable + @warn_unqualified_access + public func forEach( + _ toElementsState: WritableKeyPath>, + action toElementAction: CasePath, + @ReducerBuilder element: () -> Element, + file: StaticString = #file, + fileID: StaticString = #fileID, + line: UInt = #line + ) -> _ForEachReducer + where ElementState == Element.State, ElementAction == Element.Action { + _ForEachReducer( + parent: self, + toElementsState: toElementsState, + toElementAction: toElementAction, + element: element(), + file: file, + fileID: fileID, + line: line + ) + } +} + +public struct _ForEachReducer< + Parent: ReducerProtocol, ID: Hashable, Element: ReducerProtocol +>: ReducerProtocol { + @usableFromInline + let parent: Parent + + @usableFromInline + let toElementsState: WritableKeyPath> + + @usableFromInline + let toElementAction: CasePath + + @usableFromInline + let element: Element + + @usableFromInline + let file: StaticString + + @usableFromInline + let fileID: StaticString + + @usableFromInline + let line: UInt + + @usableFromInline + init( + parent: Parent, + toElementsState: WritableKeyPath>, + toElementAction: CasePath, + element: Element, + file: StaticString, + fileID: StaticString, + line: UInt + ) { + self.parent = parent + self.toElementsState = toElementsState + self.toElementAction = toElementAction + self.element = element + self.file = file + self.fileID = fileID + self.line = line + } + + @inlinable + public func reduce( + into state: inout Parent.State, action: Parent.Action + ) -> EffectTask { + EffectTask.merge( + self.reduceForEach(into: &state, action: action), + self.parent.reduce(into: &state, action: action) + ) + } + + @inlinable + func reduceForEach( + into state: inout Parent.State, action: Parent.Action + ) -> EffectTask { + guard let (id, elementAction) = self.toElementAction.extract(from: action) else { return .none } + if state[keyPath: self.toElementsState][id: id] == nil { + runtimeWarn( + """ + A "forEach" at "\(self.fileID):\(self.line)" received an action for a missing element. + + Action: + \(debugCaseOutput(action)) + + This is generally considered an application logic error, and can happen for a few reasons: + + • A parent reducer removed an element with this ID before this reducer ran. This reducer \ + must run before any other reducer removes an element, which ensures that element reducers \ + can handle their actions while their state is still available. + + • An in-flight effect emitted this action when state contained no element at this ID. \ + While it may be perfectly reasonable to ignore this action, consider canceling the \ + associated effect before an element is removed, especially if it is a long-living effect. + + • This action was sent to the store while its state contained no element at this ID. To \ + fix this make sure that actions for this reducer can only be sent from a view store when \ + its state contains an element at this id. In SwiftUI applications, use "ForEachStore". + """, + file: self.file, + line: self.line + ) + return .none + } + return self.element + .reduce(into: &state[keyPath: self.toElementsState][id: id]!, action: elementAction) + .map { self.toElementAction.embed((id, $0)) } + } +} diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/IfCaseLetReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/IfCaseLetReducer.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/IfCaseLetReducer.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/IfLetReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/IfLetReducer.swift new file mode 100644 index 0000000..70f823e --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/IfLetReducer.swift @@ -0,0 +1,156 @@ +extension ReducerProtocol { + /// Embeds a child reducer in a parent domain that works on an optional property of parent state. + /// + /// For example, if a parent feature holds onto a piece of optional child state, then it can + /// perform its core logic _and_ the child's logic by using the `ifLet` operator: + /// + /// ```swift + /// struct Parent: ReducerProtocol { + /// struct State { + /// var child: Child.State? + /// // ... + /// } + /// enum Action { + /// case child(Child.Action) + /// // ... + /// } + /// + /// var body: some ReducerProtocol { + /// Reduce { state, action in + /// // Core logic for parent feature + /// } + /// .ifLet(\.child, action: /Action.child) { + /// Child() + /// } + /// } + /// } + /// ``` + /// + /// The `ifLet` forces a specific order of operations for the child and parent features. It runs + /// the child first, and then the parent. If the order was reversed, then it would be possible for + /// the parent feature to `nil` out the child state, in which case the child feature would not be + /// able to react to that action. That can cause subtle bugs. + /// + /// It is still possible for a parent feature higher up in the application to `nil` out child + /// state before the child has a chance to react to the action. In such cases a runtime warning + /// is shown in Xcode to let you know that there's a potential problem. + /// + /// - Parameters: + /// - toWrappedState: A writable key path from parent state to a property containing optional + /// child state. + /// - toWrappedAction: A case path from parent action to a case containing child actions. + /// - wrapped: A reducer that will be invoked with child actions against non-optional child + /// state. + /// - Returns: A reducer that combines the child reducer with the parent reducer. + @inlinable + @warn_unqualified_access + public func ifLet( + _ toWrappedState: WritableKeyPath, + action toWrappedAction: CasePath, + @ReducerBuilder then wrapped: () -> Wrapped, + file: StaticString = #file, + fileID: StaticString = #fileID, + line: UInt = #line + ) -> _IfLetReducer + where WrappedState == Wrapped.State, WrappedAction == Wrapped.Action { + .init( + parent: self, + child: wrapped(), + toChildState: toWrappedState, + toChildAction: toWrappedAction, + file: file, + fileID: fileID, + line: line + ) + } +} + +public struct _IfLetReducer: ReducerProtocol { + @usableFromInline + let parent: Parent + + @usableFromInline + let child: Child + + @usableFromInline + let toChildState: WritableKeyPath + + @usableFromInline + let toChildAction: CasePath + + @usableFromInline + let file: StaticString + + @usableFromInline + let fileID: StaticString + + @usableFromInline + let line: UInt + + @usableFromInline + init( + parent: Parent, + child: Child, + toChildState: WritableKeyPath, + toChildAction: CasePath, + file: StaticString, + fileID: StaticString, + line: UInt + ) { + self.parent = parent + self.child = child + self.toChildState = toChildState + self.toChildAction = toChildAction + self.file = file + self.fileID = fileID + self.line = line + } + + @inlinable + public func reduce( + into state: inout Parent.State, action: Parent.Action + ) -> EffectTask { + EffectTask.merge( + self.reduceChild(into: &state, action: action), + self.parent.reduce(into: &state, action: action) + ) + } + + @inlinable + func reduceChild( + into state: inout Parent.State, action: Parent.Action + ) -> EffectTask { + guard let childAction = self.toChildAction.extract(from: action) + else { return .none } + guard state[keyPath: self.toChildState] != nil else { + runtimeWarn( + """ + An "ifLet" at "\(self.fileID):\(self.line)" received a child action when child state was \ + "nil". … + + Action: + \(debugCaseOutput(action)) + + This is generally considered an application logic error, and can happen for a few reasons: + + • A parent reducer set child state to "nil" before this reducer ran. This reducer must \ + run before any other reducer sets child state to "nil". This ensures that child reducers \ + can handle their actions while their state is still available. + + • An in-flight effect emitted this action when child state was "nil". While it may be \ + perfectly reasonable to ignore this action, consider canceling the associated effect \ + before child state becomes "nil", especially if it is a long-living effect. + + • This action was sent to the store while state was "nil". Make sure that actions for this \ + reducer can only be sent from a view store when state is non-"nil". In SwiftUI \ + applications, use "IfLetStore". + """, + file: self.file, + line: self.line + ) + return .none + } + return self.child.reduce(into: &state[keyPath: self.toChildState]!, action: childAction) + .map { self.toChildAction.embed($0) } + } +} diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/Optional.swift b/Sources/ComposableArchitecture/Reducer/Reducers/Optional.swift new file mode 100644 index 0000000..e610e65 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/Optional.swift @@ -0,0 +1,19 @@ +extension Optional: ReducerProtocol where Wrapped: ReducerProtocol { +#if swift(<5.7) + public typealias State = Wrapped.State + public typealias Action = Wrapped.Action + public typealias _Body = Never +#endif + + @inlinable + public func reduce( + into state: inout Wrapped.State, action: Wrapped.Action + ) -> EffectTask { + switch self { + case let .some(wrapped): + return wrapped.reduce(into: &state, action: action) + case .none: + return .none + } + } +} diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/Reduce.swift b/Sources/ComposableArchitecture/Reducer/Reducers/Reduce.swift new file mode 100644 index 0000000..b7f5448 --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/Reduce.swift @@ -0,0 +1,37 @@ +/// A type-erased reducer that invokes the given `reduce` function. +/// +/// ``Reduce`` is useful for injecting logic into a reducer tree without the overhead of introducing +/// a new type that conforms to ``ReducerProtocol``. +public struct Reduce: ReducerProtocol { + @usableFromInline + let reduce: (inout State, Action) -> EffectTask + + @usableFromInline + init( + internal reduce: @escaping (inout State, Action) -> EffectTask + ) { + self.reduce = reduce + } + + /// Initializes a reducer with a `reduce` function. + /// + /// - Parameter reduce: A function that is called when ``reduce(into:action:)`` is invoked. + @inlinable + public init(_ reduce: @escaping (inout State, Action) -> EffectTask) { + self.init(internal: reduce) + } + + /// Type-erases a reducer. + /// + /// - Parameter reducer: A reducer that is called when ``reduce(into:action:)`` is invoked. + @inlinable + public init(_ reducer: R) + where R.State == State, R.Action == Action { + self.init(internal: reducer.reduce) + } + + @inlinable + public func reduce(into state: inout State, action: Action) -> EffectTask { + self.reduce(&state, action) + } +} diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/Scope.swift b/Sources/ComposableArchitecture/Reducer/Reducers/Scope.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/Scope.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/SignpostReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/SignpostReducer.swift new file mode 100644 index 0000000..d2a58ee --- /dev/null +++ b/Sources/ComposableArchitecture/Reducer/Reducers/SignpostReducer.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Nguyen Phong on 4/13/23. +// + +import Foundation diff --git a/Sources/ComposableArchitecture/ReducerProtocol.swift b/Sources/ComposableArchitecture/ReducerProtocol.swift new file mode 100644 index 0000000..d97ea08 --- /dev/null +++ b/Sources/ComposableArchitecture/ReducerProtocol.swift @@ -0,0 +1,374 @@ +#if compiler(>=5.7) +/// A protocol that describes how to evolve the current state of an application to the next state, +/// given an action, and describes what ``EffectTask``s should be executed later by the store, if +/// any. +/// +/// Conform types to this protocol to represent the domain, logic and behavior for your feature. +/// The domain is specified by the "state" and "actions", which can be nested types inside the +/// conformance: +/// +/// ```swift +/// struct Feature: ReducerProtocol { +/// struct State { +/// var count = 0 +/// } +/// enum Action { +/// case decrementButtonTapped +/// case incrementButtonTapped +/// } +/// +/// // ... +/// } +/// ``` +/// +/// The logic of your feature is implemented by mutating the feature's current state when an action +/// comes into the system. This is most easily done by implementing the +/// ``ReducerProtocol/reduce(into:action:)-8yinq`` method of the protocol. +/// +/// ```swift +/// struct Feature: ReducerProtocol { +/// // ... +/// +/// func reduce(into state: inout State, action: Action) -> EffectTask { +/// switch action { +/// case .decrementButtonTapped: +/// state.count -= 1 +/// return .none +/// +/// case .incrementButtonTapped: +/// state.count += 1 +/// return .none +/// } +/// } +/// } +/// ``` +/// +/// The `reduce` method's first responsibility is to mutate the feature's current state given an +/// action. Its second responsibility is to return effects that will be executed asynchronously +/// and feed their data back into the system. Currently `Feature` does not need to run any effects, +/// and so ``EffectPublisher/none`` is returned. +/// +/// If the feature does need to do effectful work, then more would need to be done. For example, +/// suppose the feature has the ability to start and stop a timer, and with each tick of the timer +/// the `count` will be incremented. That could be done like so: +/// +/// ```swift +/// struct Feature: ReducerProtocol { +/// struct State { +/// var count = 0 +/// } +/// enum Action { +/// case decrementButtonTapped +/// case incrementButtonTapped +/// case startTimerButtonTapped +/// case stopTimerButtonTapped +/// case timerTick +/// } +/// enum TimerID {} +/// +/// func reduce(into state: inout State, action: Action) -> EffectTask { +/// switch action { +/// case .decrementButtonTapped: +/// state.count -= 1 +/// return .none +/// +/// case .incrementButtonTapped: +/// state.count += 1 +/// return .none +/// +/// case .startTimerButtonTapped: +/// return .run { send in +/// while true { +/// try await Task.sleep(for: .seconds(1)) +/// await send(.timerTick) +/// } +/// } +/// .cancellable(TimerID.self) +/// +/// case .stopTimerButtonTapped: +/// return .cancel(TimerID.self) +/// +/// case .timerTick: +/// state.count += 1 +/// return .none +/// } +/// } +/// } +/// ``` +/// +/// > Note: This sample emulates a timer by performing an infinite loop with a `Task.sleep` +/// inside. This is simple to do, but is also inaccurate since small imprecisions can accumulate. +/// It would be better to inject a clock into the feature so that you could use its `timer` +/// method. Read the and articles for more +/// information. +/// +/// That is the basics of implementing a feature as a conformance to ``ReducerProtocol``. There are +/// actually two ways to define a reducer: +/// +/// 1. You can either implement the ``reduce(into:action:)-8yinq`` method, as shown above, which +/// is given direct mutable access to application ``State`` whenever an ``Action`` is fed into +/// the system, and returns an ``EffectTask`` that can communicate with the outside world and +/// feed additional ``Action``s back into the system. +/// +/// 2. Or you can implement the ``body-swift.property-7foai`` property, which combines one or +/// more reducers together. +/// +/// At most one of these requirements should be implemented. If a conformance implements both +/// requirements, only ``reduce(into:action:)-8yinq`` will be called by the ``Store``. If your +/// reducer assembles a body from other reducers _and_ has additional business logic it needs to +/// layer onto the feature, introduce this logic into the body instead, either with ``Reduce``: +/// +/// ```swift +/// var body: some ReducerProtocol { +/// Reduce { state, action in +/// // extra logic +/// } +/// Activity() +/// Profile() +/// Settings() +/// } +/// ``` +/// +/// …or moving the extra logic to a method that is wrapped in ``Reduce``: +/// +/// ```swift +/// var body: some ReducerProtocol { +/// Reduce(self.core) +/// Activity() +/// Profile() +/// Settings() +/// } +/// +/// func core(state: inout State, action: Action) -> EffectTask { +/// // extra logic +/// } +/// ``` +/// +/// If you are implementing a custom reducer operator that transforms an existing reducer, +/// _always_ invoke the ``reduce(into:action:)-8yinq`` method, never the +/// ``body-swift.property-7foai``. For example, this operator that logs all actions sent to the +/// reducer: +/// +/// ```swift +/// extension ReducerProtocol { +/// func logActions() -> some ReducerProtocol { +/// Reduce { state, action in +/// print("Received action: \(action)") +/// return self.reduce(into: &state, action: action) +/// } +/// } +/// } +/// ``` +/// +public protocol ReducerProtocol { + /// A type that holds the current state of the reducer. + associatedtype State + + /// A type that holds all possible actions that cause the ``State`` of the reducer to change + /// and/or kick off a side ``EffectTask`` that can communicate with the outside world. + associatedtype Action + + // NB: For Xcode to favor autocompleting `var body: Body` over `var body: Never` we must use a + // type alias. + associatedtype _Body + + /// A type representing the body of this reducer. + /// + /// When you create a custom reducer by implementing the ``body-swift.property-7foai``, Swift + /// infers this type from the value returned. + /// + /// If you create a custom reducer by implementing the ``reduce(into:action:)-8yinq``, Swift + /// infers this type to be `Never`. + typealias Body = _Body + + /// Evolves the current state of the reducer to the next state. + /// + /// Implement this requirement for "primitive" reducers, or reducers that work on leaf node + /// features. To define a reducer by combining the logic of other reducers together, implement + /// the ``body-swift.property-97ymy`` requirement instead. + /// + /// - Parameters: + /// - state: The current state of the reducer. + /// - action: An action that can cause the state of the reducer to change, and/or kick off a + /// side effect that can communicate with the outside world. + /// - Returns: An effect that can communicate with the outside world and feed actions back into + /// the system. + func reduce(into state: inout State, action: Action) -> EffectTask + + /// The content and behavior of a reducer that is composed from other reducers. + /// + /// Implement this requirement when you want to incorporate the behavior of other reducers + /// together. + /// + /// Do not invoke this property directly. + /// + /// > Important: if your reducer implements the ``reduce(into:action:)-8yinq`` method, it will + /// > take precedence over this property, and only ``reduce(into:action:)-8yinq`` will be called + /// > by the ``Store``. If your reducer assembles a body from other reducers and has additional + /// > business logic it needs to layer into the system, introduce this logic into the body + /// > instead, either with ``Reduce``, or with a separate, dedicated conformance. + @ReducerBuilder + var body: Body { get } +} +#else +/// A protocol that describes how to evolve the current state of an application to the next state, +/// given an action, and describes what ``EffectTask``s should be executed later by the store, if +/// any. +/// +/// There are two ways to define a reducer: +/// +/// 1. You can either implement the ``reduce(into:action:)-8yinq`` method, which is given direct +/// mutable access to application ``State`` whenever an ``Action`` is fed into the system, +/// and returns an ``EffectTask`` that can communicate with the outside world and feed +/// additional ``Action``s back into the system. +/// +/// 2. Or you can implement the ``body-swift.property-7foai`` property, which combines one or +/// more reducers together. +/// +/// At most one of these requirements should be implemented. If a conformance implements both +/// requirements, only ``reduce(into:action:)-8yinq`` will be called by the ``Store``. If your +/// reducer assembles a body from other reducers _and_ has additional business logic it needs to +/// layer onto the feature, introduce this logic into the body instead, either with ``Reduce``: +/// +/// ```swift +/// var body: some ReducerProtocol { +/// Reduce { state, action in +/// // extra logic +/// } +/// Activity() +/// Profile() +/// Settings() +/// } +/// ``` +/// +/// ...or with a separate, dedicated conformance: +/// +/// ```swift +/// var body: some ReducerProtocol { +/// Core() +/// Activity() +/// Profile() +/// Settings() +/// } +/// struct Core: ReducerProtocol { +/// // extra logic +/// } +/// ``` +/// +/// If you are implementing a custom reducer operator that transforms an existing reducer, +/// _always_ invoke the ``reduce(into:action:)-8yinq`` method, never the +/// ``body-swift.property-7foai``. For example, this operator that logs all actions sent to the +/// reducer: +/// +/// ```swift +/// extension ReducerProtocol { +/// func logActions() -> some ReducerProtocol { +/// Reduce { state, action in +/// print("Received action: \(action)") +/// return self.reduce(into: &state, action: action) +/// } +/// } +/// } +/// ``` +public protocol ReducerProtocol { + /// A type that holds the current state of the reducer. + associatedtype State + + /// A type that holds all possible actions that cause the ``State`` of the reducer to change + /// and/or kick off a side ``EffectTask`` that can communicate with the outside world. + associatedtype Action + + // NB: For Xcode to favor autocompleting `var body: Body` over `var body: Never` we must use a + // type alias. + associatedtype _Body + + /// A type representing the body of this reducer. + /// + /// When you create a custom reducer by implementing the ``body-swift.property-7foai``, Swift + /// infers this type from the value returned. + /// + /// If you create a custom reducer by implementing the ``reduce(into:action:)-8yinq``, Swift + /// infers this type to be `Never`. + typealias Body = _Body + + /// Evolves the current state of an reducer to the next state. + /// + /// Implement this requirement for "primitive" reducers, or reducers that work on leaf node + /// features. To define a reducer by combining the logic of other reducers together, implement + /// the ``body-swift.property-7foai`` requirement instead. + /// + /// - Parameters: + /// - state: The current state of the reducer. + /// - action: An action that can cause the state of the reducer to change, and/or kick off + /// a side effect that can communicate with the outside world. + /// - Returns: An effect that can communicate with the outside world and feed actions back into + /// the system. + func reduce(into state: inout State, action: Action) -> EffectTask + + /// The content and behavior of a reducer that is composed from other reducers. + /// + /// Implement this requirement when you want to incorporate the behavior of other reducers + /// together. + /// + /// Do not invoke this property directly. + /// + /// > Important: if your reducer implements the ``reduce(into:action:)-8yinq`` method, it will + /// > take precedence over this property, and only ``reduce(into:action:)-8yinq`` will be called + /// > by the ``Store``. If your reducer assembles a body from other reducers and has additional + /// > business logic it needs to layer into the system, introduce this logic into the body + /// > instead, either with ``Reduce``, or with a separate, dedicated conformance. + @ReducerBuilder + var body: Body { get } +} +#endif + +extension ReducerProtocol where Body == Never { + /// A non-existent body. + /// + /// > Warning: Do not invoke this property directly. It will trigger a fatal error at runtime. + @_transparent + public var body: Body { + fatalError( + """ + '\(Self.self)' has no body. … + + Do not access a reducer's 'body' property directly, as it may not exist. To run a reducer, \ + call 'Reducer.reduce(into:action:)', instead. + """ + ) + } +} + +extension ReducerProtocol where Body: ReducerProtocol, Body.State == State, Body.Action == Action { + /// Invokes the ``Body-40qdd``'s implementation of ``reduce(into:action:)-8yinq``. + @inlinable + public func reduce( + into state: inout Body.State, action: Body.Action + ) -> EffectTask { + self.body.reduce(into: &state, action: action) + } +} + +// NB: This is available only in Swift 5.7.1 due to the following bug: +// https://github.com/apple/swift/issues/60550 +#if swift(>=5.7.1) +/// A convenience for constraining a ``ReducerProtocol`` conformance. Available only in Swift +/// 5.7.1. +/// +/// This allows you to specify the `body` of a ``ReducerProtocol`` conformance like so: +/// +/// ```swift +/// var body: some ReducerProtocolOf { +/// // ... +/// } +/// ``` +/// +/// …instead of the more verbose: +/// +/// ```swift +/// var body: some ReducerProtocol { +/// // ... +/// } +/// ``` +public typealias ReducerProtocolOf = ReducerProtocol +#endif diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index 9c1b42e..d131fb2 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -2,186 +2,164 @@ import Foundation import RxRelay import RxSwift -#if DEBUG - import os -#endif - /// A store represents the runtime that powers the application. It is the object that you will pass - /// around to views that need to interact with the application. - /// - /// You will typically construct a single one of these at the root of your application, and then use - /// the ``scope(state:action:)`` method to derive more focused stores that can be passed to - /// subviews: - /// - /// ```swift - /// @main - /// struct MyApp: App { - /// var body: some Scene { - /// WindowGroup { - /// RootView( - /// store: Store( - /// initialState: AppState(), - /// reducer: appReducer, - /// environment: AppEnvironment( - /// ... - /// ) - /// ) - /// ) - /// } - /// } - /// } - /// ``` - /// - /// ### Scoping - /// - /// The most important operation defined on ``Store`` is the ``scope(state:action:)`` method, which - /// allows you to transform a store into one that deals with local state and actions. This is - /// necessary for passing stores to subviews that only care about a small portion of the entire - /// application's domain. - /// - /// For example, if an application has a tab view at its root with tabs for activity, search, and - /// profile, then we can model the domain like this: - /// - /// ```swift - /// struct AppState { - /// var activity: ActivityState - /// var profile: ProfileState - /// var search: SearchState - /// } - /// - /// enum AppAction { - /// case activity(ActivityAction) - /// case profile(ProfileAction) - /// case search(SearchAction) - /// } - /// ``` - /// - /// We can construct a view for each of these domains by applying ``scope(state:action:)`` to a - /// store that holds onto the full app domain in order to transform it into a store for each - /// sub-domain: - /// - /// ```swift - /// struct AppView: View { - /// let store: Store - /// - /// var body: some View { - /// TabView { - /// ActivityView(store: self.store.scope(state: \.activity, action: AppAction.activity)) - /// .tabItem { Text("Activity") } - /// - /// SearchView(store: self.store.scope(state: \.search, action: AppAction.search)) - /// .tabItem { Text("Search") } - /// - /// ProfileView(store: self.store.scope(state: \.profile, action: AppAction.profile)) - /// .tabItem { Text("Profile") } - /// } - /// } - /// ``` - /// - /// ### Thread safety - /// - /// The `Store` class is not thread-safe, and so all interactions with an instance of ``Store`` - /// (including all of its scopes and derived ``ViewStore``s) must be done on the same thread the - /// store was created on. Further, if the store is powering a SwiftUI or UIKit view, as is - /// customary, then all interactions must be done on the _main_ thread. - /// - /// The reason stores are not thread-safe is due to the fact that when an action is sent to a store, - /// a reducer is run on the current state, and this process cannot be done from multiple threads. - /// It is possible to make this process thread-safe by introducing locks or queues, but this - /// introduces new complications: - /// - /// * If done simply with `DispatchQueue.main.async` you will incur a thread hop even when you are - /// already on the main thread. This can lead to unexpected behavior in UIKit and SwiftUI, where - /// sometimes you are required to do work synchronously, such as in animation blocks. - /// - /// * It is possible to create a scheduler that performs its work immediately when on the main - /// thread and otherwise uses `DispatchQueue.main.async` (e.g. see CombineScheduler's - /// [UIScheduler](https://github.com/pointfreeco/combine-schedulers/blob/main/Sources/CombineSchedulers/UIScheduler.swift)). - /// This introduces a lot more complexity, and should probably not be adopted without having a very - /// good reason. - /// - /// This is why we require all actions be sent from the same thread. This requirement is in the same - /// spirit of how `URLSession` and other Apple APIs are designed. Those APIs tend to deliver their - /// outputs on whatever thread is most convenient for them, and then it is your responsibility to - /// dispatch back to the main queue if that's what you need. The Composable Architecture makes you - /// responsible for making sure to send actions on the main thread. If you are using an effect that - /// may deliver its output on a non-main thread, you must explicitly perform `.receive(on:)` in - /// order to force it back on the main thread. - /// - /// This approach makes the fewest number of assumptions about how effects are created and - /// transformed, and prevents unnecessary thread hops and re-dispatching. It also provides some - /// testing benefits. If your effects are not responsible for their own scheduling, then in tests - /// all of the effects would run synchronously and immediately. You would not be able to test how - /// multiple in-flight effects interleave with each other and affect the state of your application. - /// However, by leaving scheduling out of the ``Store`` we get to test these aspects of our effects - /// if we so desire, or we can ignore if we prefer. We have that flexibility. - /// - /// #### Thread safety checks - /// - /// The store performs some basic thread safety checks in order to help catch mistakes. Stores - /// constructed via the initializer ``Store/init(initialState:reducer:environment:)`` are assumed - /// to run only on the main thread, and so a check is executed immediately to make sure that is the - /// case. Further, all actions sent to the store and all scopes (see ``Store/scope(state:action:)``) - /// of the store are also checked to make sure that work is performed on the main thread. - /// - /// If you need a store that runs on a non-main thread, which should be very rare and you should - /// have a very good reason to do so, then you can construct a store via the - /// ``Store/unchecked(initialState:reducer:environment:)`` static method to opt out of all main - /// thread checks. - /// - /// --- - /// - /// See also: ``ViewStore`` to understand how one observes changes to the state in a ``Store`` and - /// sends user actions. +/// A store represents the runtime that powers the application. It is the object that you will pass +/// around to views that need to interact with the application. +/// +/// You will typically construct a single one of these at the root of your application: +/// +/// ```swift +/// @main +/// struct MyApp: App { +/// var body: some Scene { +/// WindowGroup { +/// RootView( +/// store: Store( +/// initialState: AppReducer.State(), +/// reducer: AppReducer() +/// ) +/// ) +/// } +/// } +/// } +/// ``` +/// +/// …and then use the ``scope(state:action:)`` method to derive more focused stores that can be +/// passed to subviews. +/// +/// ### Scoping +/// +/// The most important operation defined on ``Store`` is the ``scope(state:action:)`` method, which +/// allows you to transform a store into one that deals with child state and actions. This is +/// necessary for passing stores to subviews that only care about a small portion of the entire +/// application's domain. +/// +/// For example, if an application has a tab view at its root with tabs for activity, search, and +/// profile, then we can model the domain like this: +/// +/// ```swift +/// struct State { +/// var activity: Activity.State +/// var profile: Profile.State +/// var search: Search.State +/// } +/// +/// enum Action { +/// case activity(Activity.Action) +/// case profile(Profile.Action) +/// case search(Search.Action) +/// } +/// ``` +/// +/// We can construct a view for each of these domains by applying ``scope(state:action:)`` to a +/// store that holds onto the full app domain in order to transform it into a store for each +/// sub-domain: +/// +/// ```swift +/// struct AppView: View { +/// let store: StoreOf +/// +/// var body: some View { +/// TabView { +/// ActivityView(store: self.store.scope(state: \.activity, action: App.Action.activity)) +/// .tabItem { Text("Activity") } +/// +/// SearchView(store: self.store.scope(state: \.search, action: App.Action.search)) +/// .tabItem { Text("Search") } +/// +/// ProfileView(store: self.store.scope(state: \.profile, action: App.Action.profile)) +/// .tabItem { Text("Profile") } +/// } +/// } +/// } +/// ``` +/// +/// ### Thread safety +/// +/// The `Store` class is not thread-safe, and so all interactions with an instance of ``Store`` +/// (including all of its scopes and derived ``ViewStore``s) must be done on the same thread the +/// store was created on. Further, if the store is powering a SwiftUI or UIKit view, as is +/// customary, then all interactions must be done on the _main_ thread. +/// +/// The reason stores are not thread-safe is due to the fact that when an action is sent to a store, +/// a reducer is run on the current state, and this process cannot be done from multiple threads. +/// It is possible to make this process thread-safe by introducing locks or queues, but this +/// introduces new complications: +/// +/// * If done simply with `DispatchQueue.main.async` you will incur a thread hop even when you are +/// already on the main thread. This can lead to unexpected behavior in UIKit and SwiftUI, where +/// sometimes you are required to do work synchronously, such as in animation blocks. +/// +/// * It is possible to create a scheduler that performs its work immediately when on the main +/// thread and otherwise uses `DispatchQueue.main.async` (_e.g._, see Combine Schedulers' +/// [UIScheduler][uischeduler]). +/// +/// This introduces a lot more complexity, and should probably not be adopted without having a very +/// good reason. +/// +/// This is why we require all actions be sent from the same thread. This requirement is in the same +/// spirit of how `URLSession` and other Apple APIs are designed. Those APIs tend to deliver their +/// outputs on whatever thread is most convenient for them, and then it is your responsibility to +/// dispatch back to the main queue if that's what you need. The Composable Architecture makes you +/// responsible for making sure to send actions on the main thread. If you are using an effect that +/// may deliver its output on a non-main thread, you must explicitly perform `.receive(on:)` in +/// order to force it back on the main thread. +/// +/// This approach makes the fewest number of assumptions about how effects are created and +/// transformed, and prevents unnecessary thread hops and re-dispatching. It also provides some +/// testing benefits. If your effects are not responsible for their own scheduling, then in tests +/// all of the effects would run synchronously and immediately. You would not be able to test how +/// multiple in-flight effects interleave with each other and affect the state of your application. +/// However, by leaving scheduling out of the ``Store`` we get to test these aspects of our effects +/// if we so desire, or we can ignore if we prefer. We have that flexibility. +/// +/// [uischeduler]: https://github.com/pointfreeco/combine-schedulers/blob/main/Sources/CombineSchedulers/UIScheduler.swift +/// +/// #### Thread safety checks +/// +/// The store performs some basic thread safety checks in order to help catch mistakes. Stores +/// constructed via the initializer ``init(initialState:reducer:prepareDependencies:)`` are assumed +/// to run only on the main thread, and so a check is executed immediately to make sure that is the +/// case. Further, all actions sent to the store and all scopes (see ``scope(state:action:)``) of +/// the store are also checked to make sure that work is performed on the main thread. + public final class Store { private var bufferedActions: [Action] = [] - var effectCancellables: [UUID: Disposable] = [:] - private var parentCancellable: Disposable? + @_spi(Internals) public var effectCancellables: [UUID: Disposable] = [:] private var isSending = false - private let reducer: (inout State, Action) -> Effect - var state: BehaviorRelay + var parentCancellable: Disposable? +// private let reducer: (inout State, Action) -> Effect +// var state: BehaviorRelay +//#if DEBUG +// private let mainThreadChecksEnabled: Bool +//#endif +#if swift(>=5.7) + private let reducer: any ReducerProtocol +#else + private let reducer: (inout State, Action) -> EffectTask + fileprivate var scope: AnyStoreScope? +#endif + @_spi(Internals) public var state: BehaviorRelay #if DEBUG private let mainThreadChecksEnabled: Bool #endif - /// Initializes a store from an initial state, a reducer, and an environment. - /// - /// - Parameters: - /// - initialState: The state to start the application in. - /// - reducer: The reducer that powers the business logic of the application. - /// - environment: The environment of dependencies for the application. - public convenience init( - initialState: State, - reducer: Reducer, - environment: Environment - ) { - self.init( - initialState: initialState, - reducer: reducer, - environment: environment, - mainThreadChecksEnabled: true - ) - self.threadCheck(status: .`init`) - } - - /// Initializes a store from an initial state, a reducer, and an environment, and the main thread - /// check is disabled for all interactions with this store. - /// - /// - Parameters: - /// - initialState: The state to start the application in. - /// - reducer: The reducer that powers the business logic of the application. - /// - environment: The environment of dependencies for the application. - public static func unchecked( - initialState: State, - reducer: Reducer, - environment: Environment - ) -> Self { - Self( - initialState: initialState, - reducer: reducer, - environment: environment, - mainThreadChecksEnabled: false - ) - } + /// Initializes a store from an initial state and a reducer. + /// + /// - Parameters: + /// - initialState: The state to start the application in. + /// - reducer: The reducer that powers the business logic of the application. + /// - prepareDependencies: A closure that can be used to override dependencies that will be accessed + /// by the reducer. + public convenience init( + initialState: @autoclosure () -> R.State, + reducer: R + ) where R.State == State, R.Action == Action { + self.init( + initialState: initialState(), + reducer: reducer, + mainThreadChecksEnabled: true + ) + } /// Scopes the store to one that exposes local state and actions. /// @@ -326,31 +304,32 @@ public final class Store { state toLocalState: @escaping (State) -> LocalState, action fromLocalAction: @escaping (LocalAction) -> Action ) -> Store { - self.threadCheck(status: .scope) - var isSending = false - let localStore = Store( - initialState: toLocalState(self.state.value), - reducer: .init { localState, localAction, _ in - isSending = true - defer { isSending = false } - self.send(fromLocalAction(localAction)) - localState = toLocalState(self.state.value) - return .none - }, - environment: () - ) - localStore.parentCancellable = self.state - .skip(1) - .subscribe({ [weak localStore] event in - switch event { - case .next(let newValue): - guard !isSending else { return } - localStore?.state.accept(toLocalState(newValue)) - default: - break - } - }) - return localStore + fatalError() +// self.threadCheck(status: .scope) +// var isSending = false +// let localStore = Store( +// initialState: toLocalState(self.state.value), +// reducer: .init { localState, localAction, _ in +// isSending = true +// defer { isSending = false } +// self.send(fromLocalAction(localAction)) +// localState = toLocalState(self.state.value) +// return .none +// }, +// environment: () +// ) +// localStore.parentCancellable = self.state +// .skip(1) +// .subscribe({ [weak localStore] event in +// switch event { +// case .next(let newValue): +// guard !isSending else { return } +// localStore?.state.accept(toLocalState(newValue)) +// default: +// break +// } +// }) +// return localStore } /// Scopes the store to one that exposes local state. @@ -376,26 +355,26 @@ public final class Store { self.state.accept(currentState) } - while !self.bufferedActions.isEmpty { - let action = self.bufferedActions.removeFirst() - let effect = self.reducer(¤tState, action) - - var didComplete = false - let uuid = UUID() - let effectCancellable = effect.subscribe({ [weak self] event in - switch event { - case .next(let effectAction): - self?.send(effectAction, originatingFrom: action) - default: - self?.threadCheck(status: .effectCompletion(action)) - didComplete = true - self?.effectCancellables[uuid] = nil - } - }) - if !didComplete { - self.effectCancellables[uuid] = effectCancellable - } - } +// while !self.bufferedActions.isEmpty { +// let action = self.bufferedActions.removeFirst() +// let effect = self.reducer(¤tState, action) +// +// var didComplete = false +// let uuid = UUID() +// let effectCancellable = effect.subscribe({ [weak self] event in +// switch event { +// case .next(let effectAction): +// self?.send(effectAction, originatingFrom: action) +// default: +// self?.threadCheck(status: .effectCompletion(action)) +// didComplete = true +// self?.effectCancellables[uuid] = nil +// } +// }) +// if !didComplete { +// self.effectCancellables[uuid] = effectCancellable +// } +// } } /// Returns a "stateless" store by erasing state to `Void`. @@ -418,128 +397,214 @@ public final class Store { @inline(__always) private func threadCheck(status: ThreadCheckStatus) { - if #available(iOS 12.0, *) { -#if DEBUG - guard self.mainThreadChecksEnabled && !Thread.isMainThread - else { return } - - switch status { - case let .effectCompletion(action): - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - An effect completed on a non-main thread. … - - Effect returned from: - %@ - - Make sure to use ".receive(on:)" on any effects that execute on background threads to \ - receive their output on the main thread, or create your store via "Store.unchecked" to \ - opt out of the main thread checker. - - The "Store" class is not thread-safe, and so all interactions with an instance of \ - "Store" (including all of its scopes and derived view stores) must be done on the same \ - thread. - """, - debugCaseOutput(action) - ) - - case .`init`: - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - A store initialized on a non-main thread. … - - If a store is intended to be used on a background thread, create it via \ - "Store.unchecked" to opt out of the main thread checker. - - The "Store" class is not thread-safe, and so all interactions with an instance of \ - "Store" (including all of its scopes and derived view stores) must be done on the same \ - thread. - """ - ) - - case .scope: - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - "Store.scope" was called on a non-main thread. … - - Make sure to use "Store.scope" on the main thread, or create your store via \ - "Store.unchecked" to opt out of the main thread checker. - - The "Store" class is not thread-safe, and so all interactions with an instance of \ - "Store" (including all of its scopes and derived view stores) must be done on the same \ - thread. - """ - ) - - case let .send(action, originatingAction: nil): - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - "ViewStore.send" was called on a non-main thread with: %@ … - - Make sure that "ViewStore.send" is always called on the main thread, or create your \ - store via "Store.unchecked" to opt out of the main thread checker. - - The "Store" class is not thread-safe, and so all interactions with an instance of \ - "Store" (including all of its scopes and derived view stores) must be done on the same \ - thread. - """, - debugCaseOutput(action) - ) - - case let .send(action, originatingAction: .some(originatingAction)): - os_log( - .fault, dso: rw.dso, log: rw.log, - """ - An effect published an action on a non-main thread. … - - Effect published: - %@ - - Effect returned from: - %@ - - Make sure to use ".receive(on:)" on any effects that execute on background threads to \ - receive their output on the main thread, or create this store via "Store.unchecked" to \ - disable the main thread checker. - - The "Store" class is not thread-safe, and so all interactions with an instance of \ - "Store" (including all of its scopes and derived view stores) must be done on the same \ - thread. - """, - debugCaseOutput(action), - debugCaseOutput(originatingAction) - ) - } -#endif - } else { - // Fallback on earlier versions - } + } - private init( - initialState: State, - reducer: Reducer, - environment: Environment, - mainThreadChecksEnabled: Bool - ) { - self.state = BehaviorRelay(value: initialState) - self.reducer = { state, action in reducer.run(&state, action, environment) } + init( + initialState: R.State, + reducer: R, + mainThreadChecksEnabled: Bool + ) where R.State == State, R.Action == Action { + self.state = BehaviorRelay(value: initialState) +#if swift(>=5.7) + self.reducer = reducer +#else + self.reducer = reducer.reduce +#endif +#if DEBUG + self.mainThreadChecksEnabled = mainThreadChecksEnabled +#endif + self.threadCheck(status: .`init`) + } - #if DEBUG - self.mainThreadChecksEnabled = mainThreadChecksEnabled - #endif - } - deinit { for effect in effectCancellables.values { effect.dispose() } parentCancellable?.dispose() } - - } + +/// A convenience type alias for referring to a store of a given reducer's domain. +/// +/// Instead of specifying two generics: +/// +/// ```swift +/// let store: Store +/// ``` +/// +/// You can specify a single generic: +/// +/// ```swift +/// let store: StoreOf +/// ``` +public typealias StoreOf = Store + +#if swift(>=5.7) +extension ReducerProtocol { + fileprivate func rescope( + _ store: Store, + state toChildState: @escaping (State) -> ChildState, + action fromChildAction: @escaping (ChildState, ChildAction) -> Action? + ) -> Store { + (self as? any AnyScopedReducer ?? ScopedReducer(rootStore: store)) + .rescope(store, state: toChildState, action: fromChildAction) + } +} + +private final class ScopedReducer< + RootState, RootAction, ScopedState, ScopedAction +>: ReducerProtocol { + let rootStore: Store + let toScopedState: (RootState) -> ScopedState + private let parentStores: [Any] + let fromScopedAction: (ScopedState, ScopedAction) -> RootAction? + private(set) var isSending = false + + @inlinable + init(rootStore: Store) + where RootState == ScopedState, RootAction == ScopedAction { + self.rootStore = rootStore + self.toScopedState = { $0 } + self.parentStores = [] + self.fromScopedAction = { $1 } + } + + @inlinable + init( + rootStore: Store, + state toScopedState: @escaping (RootState) -> ScopedState, + action fromScopedAction: @escaping (ScopedState, ScopedAction) -> RootAction?, + parentStores: [Any] + ) { + self.rootStore = rootStore + self.toScopedState = toScopedState + self.fromScopedAction = fromScopedAction + self.parentStores = parentStores + } + + @inlinable + func reduce( + into state: inout ScopedState, action: ScopedAction + ) -> EffectTask { + self.isSending = true + defer { + state = self.toScopedState(self.rootStore.state.value) + self.isSending = false + } + if let action = self.fromScopedAction(state, action) { +// return EffectTask(value: action) + return .none + } else { + return .none + } + } +} + +protocol AnyScopedReducer { + func rescope( + _ store: Store, + state toRescopedState: @escaping (ScopedState) -> RescopedState, + action fromRescopedAction: @escaping (RescopedState, RescopedAction) -> ScopedAction? + ) -> Store +} + +extension ScopedReducer: AnyScopedReducer { + @inlinable + func rescope( + _ store: Store, + state toRescopedState: @escaping (ScopedState) -> RescopedState, + action fromRescopedAction: @escaping (RescopedState, RescopedAction) -> ScopedAction? + ) -> Store { + let fromScopedAction = self.fromScopedAction as! (ScopedState, ScopedAction) -> RootAction? + let reducer = ScopedReducer( + rootStore: self.rootStore, + state: { _ in toRescopedState(store.state.value) }, + action: { fromRescopedAction($0, $1).flatMap { fromScopedAction(store.state.value, $0) } }, + parentStores: self.parentStores + [store] + ) + let childStore = Store( + initialState: toRescopedState(store.state.value), + reducer: reducer + ) + childStore.parentCancellable = store.state + .skip(1) + .subscribe { [weak childStore] newValue in + guard !reducer.isSending else { return } + childStore?.state.accept(toRescopedState(newValue)) + } + return childStore + } +} +#else +private protocol AnyStoreScope { + func rescope( + _ store: Store, + state toRescopedState: @escaping (ScopedState) -> RescopedState, + action fromRescopedAction: @escaping (RescopedState, RescopedAction) -> ScopedAction? + ) -> Store +} + +private struct StoreScope: AnyStoreScope { + let root: Store + let fromScopedAction: Any + + init(root: Store) { + self.init( + root: root, + fromScopedAction: { (state: RootState, action: RootAction) -> RootAction? in action } + ) + } + + private init( + root: Store, + fromScopedAction: @escaping (ScopedState, ScopedAction) -> RootAction? + ) { + self.root = root + self.fromScopedAction = fromScopedAction + } + + func rescope( + _ scopedStore: Store, + state toRescopedState: @escaping (ScopedState) -> RescopedState, + action fromRescopedAction: @escaping (RescopedState, RescopedAction) -> ScopedAction? + ) -> Store { + let fromScopedAction = self.fromScopedAction as! (ScopedState, ScopedAction) -> RootAction? + + var isSending = false + let rescopedStore = Store( + initialState: toRescopedState(scopedStore.state.value), + reducer: .init { rescopedState, rescopedAction, _ in + isSending = true + defer { isSending = false } + guard + let scopedAction = fromRescopedAction(rescopedState, rescopedAction), + let rootAction = fromScopedAction(scopedStore.state.value, scopedAction) + else { return .none } + let task = self.root.send(rootAction) + rescopedState = toRescopedState(scopedStore.state.value) + if let task = task { + return .fireAndForget { await task.cancellableValue } + } else { + return .none + } + }, + environment: () + ) + rescopedStore.parentCancellable = scopedStore.state + .dropFirst() + .sink { [weak rescopedStore] newValue in + guard !isSending else { return } + rescopedStore?.state.value = toRescopedState(newValue) + } + rescopedStore.scope = StoreScope( + root: self.root, + fromScopedAction: { + fromRescopedAction($0, $1).flatMap { fromScopedAction(scopedStore.state.value, $0) } + } + ) + return rescopedStore + } +} +#endif + diff --git a/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift b/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift index 715700e..be7b32a 100644 --- a/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift +++ b/Sources/ComposableArchitecture/TestSupport/FailingEffect.swift @@ -1,4 +1,4 @@ -import XCTestDynamicOverlay +import Foundation extension Effect { /// An effect that causes a test to fail if it runs. @@ -83,7 +83,7 @@ extension Effect { /// - Returns: An effect that causes a test to fail if it runs. public static func failing(_ prefix: String) -> Self { .fireAndForget { - XCTFail("\(prefix.isEmpty ? "" : "\(prefix) - ")A failing effect ran.") +// XCTFail("\(prefix.isEmpty ? "" : "\(prefix) - ")A failing effect ran.") } } } diff --git a/Sources/ComposableArchitecture/UIKit/Binding.swift b/Sources/ComposableArchitecture/UIKit/Binding.swift new file mode 100644 index 0000000..ab68d32 --- /dev/null +++ b/Sources/ComposableArchitecture/UIKit/Binding.swift @@ -0,0 +1,434 @@ +//import RxSwift +///// A property wrapper type that can designate properties of app state that can be directly bindable +///// in SwiftUI views. +///// +///// Along with an action type that conforms to the ``BindableAction`` protocol, this type can be +///// used to safely eliminate the boilerplate that is typically incurred when working with multiple +///// mutable fields on state. +///// +///// Read for more information. +//@dynamicMemberLookup +//@propertyWrapper +//public struct BindingState { +// /// The underlying value wrapped by the bindable state. +// public var wrappedValue: Value +// +// /// Creates bindable state from the value of another bindable state. +// public init(wrappedValue: Value) { +// self.wrappedValue = wrappedValue +// } +// +// /// A projection that can be used to derive bindings from a view store. +// /// +// /// Use the projected value to derive bindings from a view store with properties annotated with +// /// `@BindingState`. To get the `projectedValue`, prefix the property with `$`: +// /// +// /// ```swift +// /// TextField("Display name", text: viewStore.binding(\.$displayName)) +// /// ``` +// /// +// /// See ``BindingState`` for more details. +// public var projectedValue: Self { +// get { self } +// set { self = newValue } +// } +// +// /// Returns bindable state to the resulting value of a given key path. +// /// +// /// - Parameter keyPath: A key path to a specific resulting value. +// /// - Returns: A new bindable state. +// public subscript( +// dynamicMember keyPath: WritableKeyPath +// ) -> BindingState { +// get { .init(wrappedValue: self.wrappedValue[keyPath: keyPath]) } +// set { self.wrappedValue[keyPath: keyPath] = newValue.wrappedValue } +// } +//} +// +//extension BindingState: Equatable where Value: Equatable {} +// +//extension BindingState: Hashable where Value: Hashable {} +// +//extension BindingState: Decodable where Value: Decodable { +// public init(from decoder: Decoder) throws { +// do { +// let container = try decoder.singleValueContainer() +// self.init(wrappedValue: try container.decode(Value.self)) +// } catch { +// self.init(wrappedValue: try Value(from: decoder)) +// } +// } +//} +// +//extension BindingState: Encodable where Value: Encodable { +// public func encode(to encoder: Encoder) throws { +// do { +// var container = encoder.singleValueContainer() +// try container.encode(self.wrappedValue) +// } catch { +// try self.wrappedValue.encode(to: encoder) +// } +// } +//} +// +//extension BindingState: CustomReflectable { +// public var customMirror: Mirror { +// Mirror(reflecting: self.wrappedValue) +// } +//} +// +//extension BindingState: CustomDebugStringConvertible where Value: CustomDebugStringConvertible { +// public var debugDescription: String { +// self.wrappedValue.debugDescription +// } +//} +// +//extension BindingState: Sendable where Value: Sendable {} +// +///// An action type that exposes a `binding` case that holds a ``BindingAction``. +///// +///// Used in conjunction with ``BindingState`` to safely eliminate the boilerplate typically +///// associated with mutating multiple fields in state. +///// +///// Read for more information. +//public protocol BindableAction { +// /// The root state type that contains bindable fields. +// associatedtype State +// +// /// Embeds a binding action in this action type. +// /// +// /// - Returns: A binding action. +// static func binding(_ action: BindingAction) -> Self +//} +// +//extension BindableAction { +// /// Constructs a binding action for the given key path and bindable value. +// /// +// /// Shorthand for `.binding(.set(\.$keyPath, value))`. +// /// +// /// - Returns: A binding action. +// public static func set( +// _ keyPath: WritableKeyPath>, +// _ value: Value +// ) -> Self { +// self.binding(.set(keyPath, value)) +// } +//} +// +//extension ViewStore where ViewAction: BindableAction, ViewAction.State == ViewState { +// /// Returns a binding to the resulting bindable state of a given key path. +// /// +// /// - Parameter keyPath: A key path to a specific bindable state. +// /// - Returns: A new binding. +// public func binding( +// _ keyPath: WritableKeyPath>, +// file: StaticString = #file, +// fileID: StaticString = #fileID, +// line: UInt = #line +// ) -> Binder { +// Binder(self) { weakSelf, action in +// weakSelf.send(action) +// } +// self.binding( +// get: { $0[keyPath: keyPath].wrappedValue }, +// send: { value in +//#if DEBUG +// let debugger = BindableActionViewStoreDebugger( +// value: value, bindableActionType: ViewAction.self, file: file, fileID: fileID, +// line: line +// ) +// let set: (inout ViewState) -> Void = { +// $0[keyPath: keyPath].wrappedValue = value +// debugger.wasCalled = true +// } +//#else +// let set: (inout ViewState) -> Void = { $0[keyPath: keyPath].wrappedValue = value } +//#endif +// return .binding(.init(keyPath: keyPath, set: set, value: value)) +// } +// ) +// } +//} +// +///// An action that describes simple mutations to some root state at a writable key path. +///// +///// Used in conjunction with ``BindingState`` and ``BindableAction`` to safely eliminate the +///// boilerplate typically associated with mutating multiple fields in state. +///// +///// Read for more information. +//public struct BindingAction: Equatable { +// public let keyPath: PartialKeyPath +// +// @usableFromInline +// let set: (inout Root) -> Void +// // NB: swift(<5.8) has an enum existential layout bug that can cause crashes when extracting +// // payloads. We can box the existential to work around the bug. +//#if swift(<5.8) +// private let _value: [Any] +// var value: Any { self._value[0] } +//#else +// let value: Any +//#endif +// let valueIsEqualTo: (Any) -> Bool +// +// init( +// keyPath: PartialKeyPath, +// set: @escaping (inout Root) -> Void, +// value: Any, +// valueIsEqualTo: @escaping (Any) -> Bool +// ) { +// self.keyPath = keyPath +// self.set = set +//#if swift(<5.8) +// self._value = [value] +//#else +// self.value = value +//#endif +// self.valueIsEqualTo = valueIsEqualTo +// } +// +// public static func == (lhs: Self, rhs: Self) -> Bool { +// lhs.keyPath == rhs.keyPath && lhs.valueIsEqualTo(rhs.value) +// } +//} +// +//extension BindingAction { +// /// Returns an action that describes simple mutations to some root state at a writable key path +// /// to bindable state. +// /// +// /// - Parameters: +// /// - keyPath: A key path to the property that should be mutated. This property must be +// /// annotated with the ``BindingState`` property wrapper. +// /// - value: A value to assign at the given key path. +// /// - Returns: An action that describes simple mutations to some root state at a writable key +// /// path. +// public static func set( +// _ keyPath: WritableKeyPath>, +// _ value: Value +// ) -> Self { +// return .init( +// keyPath: keyPath, +// set: { $0[keyPath: keyPath].wrappedValue = value }, +// value: value +// ) +// } +// +// /// Matches a binding action by its key path. +// /// +// /// Implicitly invoked when switching on a reducer's action and pattern matching on a binding +// /// action directly to do further work: +// /// +// /// ```swift +// /// case .binding(\.$displayName): // Invokes the `~=` operator. +// /// // Validate display name +// /// +// /// case .binding(\.$enableNotifications): +// /// // Return an authorization request effect +// /// ``` +// public static func ~= ( +// keyPath: WritableKeyPath>, +// bindingAction: Self +// ) -> Bool { +// keyPath == bindingAction.keyPath +// } +// +// init( +// keyPath: WritableKeyPath>, +// set: @escaping (inout Root) -> Void, +// value: Value +// ) { +// self.init( +// keyPath: keyPath, +// set: set, +// value: value, +// valueIsEqualTo: { $0 as? Value == value } +// ) +// } +//} +// +//extension BindingAction { +// /// Transforms a binding action over some root state to some other type of root state given a +// /// key path. +// /// +// /// Useful in transforming binding actions on view state into binding actions on reducer state +// /// when the domain contains ``BindingState`` and ``BindableAction``. +// /// +// /// For example, we can model an feature that can bind an integer count to a stepper and make a +// /// network request to fetch a fact about that integer with the following domain: +// /// +// /// ```swift +// /// struct MyFeature: ReducerProtocol { +// /// struct State: Equatable { +// /// @BindingState var count = 0 +// /// var fact: String? +// /// ... +// /// } +// /// +// /// enum Action: BindableAction { +// /// case binding(BindingAction) +// /// case factButtonTapped +// /// case factResponse(String?) +// /// ... +// /// } +// /// +// /// @Dependency(\.numberFact) var numberFact +// /// +// /// var body: some ReducerProtocol { +// /// BindingReducer() +// /// // ... +// /// } +// /// } +// /// +// /// struct MyFeatureView: View { +// /// let store: StoreOf +// /// +// /// var view: some View { +// /// // ... +// /// } +// /// } +// /// ``` +// /// +// /// The view may want to limit the state and actions it has access to by introducing a +// /// view-specific domain that contains only the state and actions the view needs. Not only will +// /// this minimize the number of times a view's `body` is computed, it will prevent the view +// /// from accessing state or sending actions outside its purview. We can define it with its own +// /// bindable state and bindable action: +// /// +// /// ```swift +// /// extension MyFeatureView { +// /// struct ViewState: Equatable { +// /// @BindingState var count: Int +// /// let fact: String? +// /// // no access to any other state on `MyFeature.State`, like child domains +// /// } +// /// +// /// enum ViewAction: BindableAction { +// /// case binding(BindingAction) +// /// case factButtonTapped +// /// // no access to any other action on `MyFeature.Action`, like `factResponse` +// /// } +// /// } +// /// ``` +// /// +// /// In order to transform a `BindingAction` sent from the view domain into a +// /// `BindingAction`, we need a writable key path from `MyFeature.State` to +// /// `ViewState`. We can synthesize one by defining a computed property on `MyFeature.State` with a +// /// getter and a setter. The setter should communicate any mutations to bindable state back to the +// /// parent state: +// /// +// /// ```swift +// /// extension MyFeature.State { +// /// var view: MyFeatureView.ViewState { +// /// get { .init(count: self.count, fact: self.fact) } +// /// set { self.count = newValue.count } +// /// } +// /// } +// /// ``` +// /// +// /// With this property defined it is now possible to transform a `BindingAction` into +// /// a `BindingAction`, which means we can transform a `ViewAction` into an +// /// `MyFeature.Action`. This is where `pullback` comes into play: we can unwrap the view action's +// /// binding action on view state and transform it with `pullback` to work with feature state. We +// /// can define a helper that performs this transformation, as well as route any other view actions +// /// to their reducer equivalents: +// /// +// /// ```swift +// /// extension MyFeature.Action { +// /// static func view(_ viewAction: MyFeatureView.ViewAction) -> Self { +// /// switch viewAction { +// /// case let .binding(action): +// /// // transform view binding actions into feature binding actions +// /// return .binding(action.pullback(\.view)) +// /// +// /// case let .factButtonTapped +// /// // route `ViewAction.factButtonTapped` to `MyFeature.Action.factButtonTapped` +// /// return .factButtonTapped +// /// } +// /// } +// /// } +// /// ``` +// /// +// /// Finally, in the view we can invoke ``Store/scope(state:action:)`` with these domain +// /// transformations to leverage the view store's binding helpers: +// /// +// /// ```swift +// /// WithViewStore( +// /// self.store, observe: \.view, send: MyFeature.Action.view +// /// ) { viewStore in +// /// Stepper("\(viewStore.count)", viewStore.binding(\.$count)) +// /// Button("Get number fact") { viewStore.send(.factButtonTapped) } +// /// if let fact = viewStore.fact { +// /// Text(fact) +// /// } +// /// } +// /// ``` +// /// +// /// - Parameter keyPath: A key path from a new type of root state to the original root state. +// /// - Returns: A binding action over a new type of root state. +// public func pullback( +// _ keyPath: WritableKeyPath +// ) -> BindingAction { +// .init( +// keyPath: (keyPath as AnyKeyPath).appending(path: self.keyPath) as! PartialKeyPath, +// set: { self.set(&$0[keyPath: keyPath]) }, +// value: self.value, +// valueIsEqualTo: self.valueIsEqualTo +// ) +// } +//} +// +//extension BindingAction: CustomDumpReflectable { +// public var customDumpMirror: Mirror { +// Mirror( +// self, +// children: [ +// "set": (self.keyPath, self.value) +// ], +// displayStyle: .enum +// ) +// } +//} +// +//#if DEBUG +//private final class BindableActionViewStoreDebugger { +// let value: Value +// let bindableActionType: Any.Type +// let file: StaticString +// let fileID: StaticString +// let line: UInt +// var wasCalled = false +// +// init( +// value: Value, +// bindableActionType: Any.Type, +// file: StaticString, +// fileID: StaticString, +// line: UInt +// ) { +// self.value = value +// self.bindableActionType = bindableActionType +// self.file = file +// self.fileID = fileID +// self.line = line +// } +// +// deinit { +// guard self.wasCalled else { +// runtimeWarn( +// """ +// A binding action sent from a view store at "\(self.fileID):\(self.line)" was not \ +// handled. … +// +// Action: +// \(typeName(self.bindableActionType)).binding(.set(_, \(self.value))) +// +// To fix this, invoke "BindingReducer()" from your feature reducer's "body". +// """, +// file: self.file, +// line: self.line +// ) +// return +// } +// } +//} +//#endif diff --git a/Sources/ComposableArchitecture/ViewStore.swift b/Sources/ComposableArchitecture/ViewStore.swift index 5f56900..7742434 100644 --- a/Sources/ComposableArchitecture/ViewStore.swift +++ b/Sources/ComposableArchitecture/ViewStore.swift @@ -1,148 +1,354 @@ import RxRelay import RxSwift - /// A ``ViewStore`` is an object that can observe state changes and send actions. They are most - /// commonly used in views, such as SwiftUI views, UIView or UIViewController, but they can be - /// used anywhere it makes sense to observe state and send actions. - /// - /// In SwiftUI applications, a ``ViewStore`` is accessed most commonly using the ``WithViewStore`` - /// view. It can be initialized with a store and a closure that is handed a view store and must - /// return a view to be rendered: - /// - /// ```swift - /// var body: some View { - /// WithViewStore(self.store) { viewStore in - /// VStack { - /// Text("Current count: \(viewStore.count)") - /// Button("Increment") { viewStore.send(.incrementButtonTapped) } - /// } - /// } - /// } - /// ``` - /// - /// In UIKit applications a ``ViewStore`` can be created from a ``Store`` and then subscribed to for - /// state updates: - /// - /// ```swift - /// let store: Store - /// let viewStore: ViewStore +/// A ``ViewStore`` is an object that can observe state changes and send actions. They are most +/// commonly used in views, such as SwiftUI views, UIView or UIViewController, but they can be +/// used anywhere it makes sense to observe state and send actions. +/// +/// In SwiftUI applications, a ``ViewStore`` is accessed most commonly using the ``WithViewStore`` +/// view. It can be initialized with a store and a closure that is handed a view store and must +/// return a view to be rendered: +/// +/// ```swift +/// var body: some View { +/// WithViewStore(self.store) { viewStore in +/// VStack { +/// Text("Current count: \(viewStore.count)") +/// Button("Increment") { viewStore.send(.incrementButtonTapped) } +/// } +/// } +/// } +/// ``` +/// +/// In UIKit applications a ``ViewStore`` can be created from a ``Store`` and then subscribed to for +/// state updates: +/// +/// ```swift +/// let store: Store +/// let viewStore: ViewStore +/// +/// init(store: Store) { +/// self.store = store +/// self.viewStore = ViewStore(store) +/// } +/// +/// func viewDidLoad() { +/// super.viewDidLoad() +/// +/// self.viewStore.publisher.count +/// .sink { [weak self] in self?.countLabel.text = $0 } +/// .store(in: &self.cancellables) +/// } +/// +/// @objc func incrementButtonTapped() { +/// self.viewStore.send(.incrementButtonTapped) +/// } +/// ``` +/// +/// ### Thread safety +/// +/// The ``ViewStore`` class is not thread-safe, and all interactions with it (and the store it was +/// derived from) must happen on the same thread. Further, for SwiftUI applications, all +/// interactions must happen on the _main_ thread. See the documentation of the ``Store`` class for +/// more information as to why this decision was made. +@dynamicMemberLookup +public final class ViewStore { + private let _send: (ViewAction) -> Void + fileprivate var _state: BehaviorRelay + private var viewDisposable: Disposable? + deinit { + viewDisposable?.dispose() + } + /// Initializes a view store from a store. /// - /// init(store: Store) { - /// self.store = store - /// self.viewStore = ViewStore(store) - /// } + /// - Parameters: + /// - store: A store. + /// - isDuplicate: A function to determine when two `State` values are equal. When values are + /// equal, repeat view computations are removed. + public init( + _ store: Store, + observe toViewState: @escaping (State) -> ViewState, + removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool + ) { + self._send = { store.send($0) } + self._state = BehaviorRelay(value: toViewState(store.state.value)) + self.viewDisposable = store.state + .map(toViewState) + .distinctUntilChanged(isDuplicate).subscribe(onNext: { [weak self] in + guard let self = self else { return } + self._state.accept($0) + }) + } + /// Initializes a view store from a store which observes changes to state. /// - /// func viewDidLoad() { - /// super.viewDidLoad() + /// It is recommended that the `observe` argument transform the store's state into the bare + /// minimum of data needed for the feature to do its job in order to not hinder performance. + /// This is especially true for root level features, and less important for leaf features. /// - /// self.viewStore.publisher.count - /// .sink { [weak self] in self?.countLabel.text = $0 } - /// .store(in: &self.cancellables) - /// } + /// To read more about this performance technique, read the article. /// - /// @objc func incrementButtonTapped() { - /// self.viewStore.send(.incrementButtonTapped) - /// } - /// ``` + /// - Parameters: + /// - store: A store. + /// - toViewState: A transformation of `ViewState` to the state that will be observed for + /// changes. + /// - fromViewAction: A transformation of `ViewAction` that describes what actions can be sent. + /// - isDuplicate: A function to determine when two `State` values are equal. When values are + /// equal, repeat view computations are removed. + public init( + _ store: Store, + observe toViewState: @escaping (State) -> ViewState, + send fromViewAction: @escaping (ViewAction) -> Action, + removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool + ) { + self._send = { store.send(fromViewAction($0)) } + self._state = BehaviorRelay(value: toViewState(store.state.value)) + self.viewDisposable = store.state + .map(toViewState) + .distinctUntilChanged(isDuplicate).subscribe(onNext: { [weak self] in + guard let self = self else { return } + self._state.accept($0) + }) + } + + /// Initializes a view store from a store. /// - /// ### Thread safety + /// > Warning: This initializer is deprecated. Use + /// ``ViewStore/init(_:observe:removeDuplicates:)`` to make state observation explicit. + /// > + /// > When using ``ViewStore`` you should take care to observe only the pieces of state that + /// your view needs to do its job, especially towards the root of the application. See + /// for more details. /// - /// The ``ViewStore`` class is not thread-safe, and all interactions with it (and the store it was - /// derived from) must happen on the same thread. Further, for SwiftUI applications, all - /// interactions must happen on the _main_ thread. See the documentation of the ``Store`` class for - /// more information as to why this decision was made. -@dynamicMemberLookup -public final class ViewStore { - private let _send: (Action) -> Void - fileprivate var _state: BehaviorRelay - private var viewDisposable: Disposable? - deinit { - viewDisposable?.dispose() - } - /// Initializes a view store from a store. - /// - /// - Parameters: - /// - store: A store. - /// - isDuplicate: A function to determine when two `State` values are equal. When values are - /// equal, repeat view computations are removed. - public init(_ store: Store, removeDuplicates isDuplicate: @escaping (State, State) -> Bool) { + /// - Parameters: + /// - store: A store. + /// - isDuplicate: A function to determine when two `State` values are equal. When values are + /// equal, repeat view computations are removed. + @available( + iOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:removeDuplicates:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + @available( + macOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:removeDuplicates:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + @available( + tvOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:removeDuplicates:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + @available( + watchOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:removeDuplicates:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + public init( + _ store: Store, + removeDuplicates isDuplicate: @escaping (ViewState, ViewState) -> Bool + ) { self._send = { store.send($0) } self._state = BehaviorRelay(value: store.state.value) self.viewDisposable = store.state - .distinctUntilChanged(isDuplicate).subscribe(onNext: { [weak self] in + .distinctUntilChanged(isDuplicate) + .subscribe(onNext: { [weak self] in guard let self = self else { return } self._state.accept($0) }) } - /// A publisher that emits when state changes. - /// - /// This publisher supports dynamic member lookup so that you can pluck out a specific field in - /// the state: - /// - /// ```swift - /// viewStore.publisher.alert - /// .sink { ... } - /// ``` - /// - /// When the emission happens the ``ViewStore``'s state has been updated, and so the following - /// precondition will pass: - /// - /// ```swift - /// viewStore.publisher - /// .sink { precondition($0 == viewStore.state) } - /// ``` - /// - /// This means you can either use the value passed to the closure or you can reach into - /// `viewStore.state` directly. - /// - /// - Note: Due to a bug in Combine (or feature?), the order you `.sink` on a publisher has no - /// bearing on the order the `.sink` closures are called. This means the work performed inside - /// `viewStore.publisher.sink` closures should be completely independent of each other. - /// Later closures cannot assume that earlier ones have already run. - public var publisher: StorePublisher { + + init(_ viewStore: ViewStore) { + self._send = viewStore._send + self._state = viewStore._state + self.viewDisposable = viewStore.viewDisposable + } + + /// A publisher that emits when state changes. + /// + /// This publisher supports dynamic member lookup so that you can pluck out a specific field in + /// the state: + /// + /// ```swift + /// viewStore.publisher.alert + /// .sink { ... } + /// ``` + /// + /// When the emission happens the ``ViewStore``'s state has been updated, and so the following + /// precondition will pass: + /// + /// ```swift + /// viewStore.publisher + /// .sink { precondition($0 == viewStore.state) } + /// ``` + /// + /// This means you can either use the value passed to the closure or you can reach into + /// `viewStore.state` directly. + /// + /// - Note: Due to a bug in Combine (or feature?), the order you `.sink` on a publisher has no + /// bearing on the order the `.sink` closures are called. This means the work performed inside + /// `viewStore.publisher.sink` closures should be completely independent of each other. Later + /// closures cannot assume that earlier ones have already run. + public var publisher: StorePublisher { StorePublisher(viewStore: self) } - /// The current state. - public var state: State { + /// The current state. + public var state: ViewState { self._state.value } - /// Returns the resulting value of a given key path. - public subscript(dynamicMember keyPath: KeyPath) -> LocalState { + /// Returns the resulting value of a given key path. + public subscript(dynamicMember keyPath: KeyPath) -> LocalState { self._state.value[keyPath: keyPath] } - /// The Binder action. - public var action: Binder { + /// The Binder action. + public var action: Binder { Binder(self) { weakSelf, action in weakSelf.send(action) } } - /// Sends an action to the store. - /// - /// ``ViewStore`` is not thread safe and you should only send actions to it from the main thread. - /// If you are wanting to send actions on background threads due to the fact that the reducer - /// is performing computationally expensive work, then a better way to handle this is to wrap - /// that work in an ``Effect`` that is performed on a background thread so that the result can - /// be fed back into the store. - /// - /// - Parameter action: An action. - public func send(_ action: Action) { + /// Sends an action to the store. + /// + /// ``ViewStore`` is not thread safe and you should only send actions to it from the main thread. + /// If you are wanting to send actions on background threads due to the fact that the reducer + /// is performing computationally expensive work, then a better way to handle this is to wrap + /// that work in an ``Effect`` that is performed on a background thread so that the result can + /// be fed back into the store. + /// + /// - Parameter action: An action. + public func send(_ action: ViewAction) { self._send(action) } } -extension ViewStore where State: Equatable { - public convenience init(_ store: Store) { +/// A convenience type alias for referring to a view store of a given reducer's domain. +/// +/// Instead of specifying two generics: +/// +/// ```swift +/// let viewStore: ViewStore +/// ``` +/// +/// You can specify a single generic: +/// +/// ```swift +/// let viewStore: ViewStoreOf +/// ``` +//public typealias ViewStoreOf = ViewStore + +extension ViewStore where ViewState: Equatable { + public convenience init( + _ store: Store, + observe toViewState: @escaping (State) -> ViewState + ) { + self.init(store, observe: toViewState, removeDuplicates: ==) + } + + public convenience init( + _ store: Store, + observe toViewState: @escaping (State) -> ViewState, + send fromViewAction: @escaping (ViewAction) -> Action + ) { + self.init(store, observe: toViewState, send: fromViewAction, removeDuplicates: ==) + } + + /// Initializes a view store from a store. + /// + /// > Warning: This initializer is deprecated. Use + /// ``ViewStore/init(_:observe:)`` to make state observation explicit. + /// > + /// > When using ``ViewStore`` you should take care to observe only the pieces of state that + /// your view needs to do its job, especially towards the root of the application. See + /// for more details. + /// + /// - Parameters: + /// - store: A store. + @available( + iOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + @available( + macOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + @available( + tvOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + @available( + watchOS, + deprecated: 9999.0, + message: + """ + Use 'init(_:observe:)' to make state observation explicit. + + When using ViewStore you should take care to observe only the pieces of state that your view needs to do its job, especially towards the root of the application. See the performance article for more details: + + https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/performance#View-stores + """ + ) + public convenience init(_ store: Store) { self.init(store, removeDuplicates: ==) } } -extension ViewStore where State == Void { - public convenience init(_ store: Store) { +extension ViewStore where ViewState == Void { + public convenience init(_ store: Store) { self.init(store, removeDuplicates: ==) } } - /// A publisher of store state. +/// A publisher of store state. @dynamicMemberLookup public struct StorePublisher: ObservableType { public typealias Element = State @@ -167,8 +373,10 @@ public struct StorePublisher: ObservableType { self.viewStore = viewStore } - /// Returns the resulting publisher of a given key path. - public subscript(dynamicMember keyPath: KeyPath) -> StorePublisher where LocalState: Equatable { + /// Returns the resulting publisher of a given key path. + public subscript( + dynamicMember keyPath: KeyPath + ) -> StorePublisher where LocalState: Equatable { .init(self.upstream.map { $0[keyPath: keyPath] }.distinctUntilChanged(), viewStore: viewStore) } }