-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathBindableReducer.swift
More file actions
165 lines (137 loc) · 6.43 KB
/
BindableReducer.swift
File metadata and controls
165 lines (137 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//===--- BindableReducer.swift ------------------------------------===//
//
// This source file is part of the UDF open source project
//
// Copyright (c) 2024 You are launched
// Licensed under Apache License v2.0
//
// See https://opensource.org/licenses/Apache-2.0 for license information
//
//===----------------------------------------------------------------------===//
import Foundation
/// A property wrapper that manages a collection of reducers bound to a specific container type.
///
/// `BindableReducer` is designed to automatically manage multiple instances of reducers associated with different instances
/// of a `BindableContainer`. It allows actions to be dispatched and reduced in a dynamic, container-bound manner, facilitating
/// the organization of complex state management in a SwiftUI application.
@propertyWrapper
public struct BindableReducer<BindedContainer: BindableContainer, Reducer: Reducible>: Reducible where BindedContainer.ID: Sendable {
/// A typealias representing a dictionary of reducers associated with container IDs.
public typealias Reducers = RCDictionary<BindedContainer.ID, Reducer>
/// The type of container this reducer is bound to.
public internal(set) var containerType: BindedContainer.Type
/// The dictionary holding the reducers associated with each container ID.
var reducers: Reducers = .init()
/// The wrapped value, which returns `self`.
public var wrappedValue: BindableReducer<BindedContainer, Reducer> {
get { self }
set { /* do nothing */ }
}
/// Initializes a new `BindableReducer` with the specified reducer and container types.
///
/// - Parameters:
/// - reducerType: The type of reducer to manage.
/// - bindedTo: The type of container to bind this reducer to.
public init(_ reducerType: Reducer.Type, bindedTo: BindedContainer.Type) {
self.containerType = bindedTo
}
/// Throws a fatal error. Use `init(reducerType:bindedTo:)` instead.
@available(*, deprecated, message: "Use `init(reducerType:bindedTo:)` instead.")
public init() {
fatalError("use init(containerType:reducerType:) instead")
}
/// Checks for equality between two `BindableReducer` instances by comparing their reducers.
public static func == (lhs: BindableReducer<BindedContainer, Reducer>, rhs: BindableReducer<BindedContainer, Reducer>) -> Bool {
lhs.reducers == rhs.reducers
}
/// Subscript to access the reducer associated with the specified container ID.
///
/// - Parameter id: The ID of the container.
/// - Returns: The reducer associated with the given container ID, if it exists.
public subscript(_ id: BindedContainer.ID) -> Reducer? {
reducers[id]
}
/// Subscript to access the `Scope` of the reducer associated with the specified container ID.
///
/// - Parameter id: The ID of the container.
/// - Returns: A `ReducerScope` for the associated reducer, or `nil` if no reducer is found.
public subscript(_ id: BindedContainer.ID) -> Scope {
ReducerScope(reducer: reducers[id])
}
}
// MARK: - Collection Conformance
extension BindableReducer: Collection {
public typealias Index = Reducers.Index
public typealias Element = (key: BindedContainer.ID, value: Reducer)
/// The starting index of the collection, used in iterations.
public var startIndex: Index { reducers.startIndex }
/// The ending index of the collection, used in iterations.
public var endIndex: Index { reducers.endIndex }
/// Required subscript to access an element of the collection at the specified index.
///
/// - Parameter index: The position in the collection.
/// - Returns: The element at the specified index.
public subscript(index: Index) -> Element {
let element = reducers[index]
return (element.key, element.value)
}
/// Returns the next index in the collection.
///
/// - Parameter i: The current index.
/// - Returns: The index immediately after the given index.
public func index(after i: Index) -> Index {
reducers.index(after: i)
}
}
// MARK: - Runtime Reducing
public extension BindableReducer {
/// Reduces an action by managing its effects on the collection of bound reducers.
///
/// This method handles specific actions to manage the lifecycle of reducers (`_OnContainerDidLoad`, `_OnContainerDidUnLoad`, and
/// `_BindableAction`),
/// adding, removing, or reducing the appropriate reducers based on the action's type.
///
/// - Parameter action: The action to be reduced.
mutating func reduce(_ action: some Action) where BindedContainer.ID: Sendable {
switch action {
case let action as Actions._OnContainerDidLoad<BindedContainer>:
reducers.retainOrCreateValue(for: action.id)
case let action as Actions._OnContainerDidUnLoad<BindedContainer>:
reducers.release(key: action.id)
case let action as Actions._BindableAction<BindedContainer>:
for (key, var reducer) in reducers where key == action.id {
_ = RuntimeReducing.bindableReduce(action.value, reducer: &reducer)
reducers.updateValue(reducer, forKey: key)
}
default:
break
}
}
}
// MARK: - AnyBindableReducer
extension BindableReducer: AnyBindableReducer {
/// The type of container this reducer is bound to.
var boundContainerType: any Any.Type {
containerType
}
/// Checks if a reducer is registered for the specified container identifier.
///
/// - Parameter id: The identifier of the container, expected to be of type `BindedContainer.ID`.
/// - Returns: `true` if a reducer exists for the given identifier; otherwise, `false`.
func hasReducer(for id: any Hashable) -> Bool {
guard let id = id as? BindedContainer.ID else {
return false
}
return reducers.contains { $0.key == id }
}
/// Determines whether the reducer for the given identifier is the last active instance.
///
/// - Parameter id: The identifier of the container, expected to be of type `BindedContainer.ID`.
/// - Returns: `true` if the reducer is uniquely referenced; otherwise, `false`.
func isLastInstance(for id: any Hashable) -> Bool {
guard let containerID = id as? BindedContainer.ID else {
return false
}
return reducers.isUniquelyReferenced(key: containerID)
}
}