Replies: 3 comments 2 replies
-
For context, here is my old This ergonomic is a personal priority to support readability and reduce academic overhead. It is not the condoned approach for child dismissal by the library. public extension Reducer {
func optional(
lenient: Bool,
teardownDeadline: TimeInterval = 1.0,
file: StaticString = #fileID,
line: UInt = #line
) -> Reducer<State?, Action, Environment> {
var _oldState: State?
var oldState: State? {
get { _oldState }
set {
Log.warning("Ignoring state mutation during teardown")
_oldState = newValue
}
}
var nilTime: Date?
return .init { state, action, env in
_oldState = state ?? _oldState
guard state == nil else {
nilTime = nil
return optional(file: file, line: line).run(&state, action, env)
}
nilTime = nilTime ?? Date()
if let nilTime = nilTime, Date().timeIntervalSince(nilTime) > teardownDeadline {
Log.warning("Optional reducer teardown leniency expired")
return optional(file: file, line: line).run(&state, action, env)
}
if var oldState = oldState, lenient {
return run(&oldState, action, env)
}
return optional(file: file, line: line).run(&state, action, env)
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Regardless of the nobility of the above example, I think migration options would be to offload the state caching to a private top-level dictionary. I would need to figure out how to hash the id of the reducer. The hashable components we have at our disposal are:
I think |
Beta Was this translation helpful? Give feedback.
-
@heyltsjay Thanks for posting :) Are you using Another option could be to incorporate a property wrapper. Here's a rough example: @propertyWrapper
public struct Lenient<Wrapped> {
var lenientValue: Wrapped?
var nilDate: Date?
@Dependency(\.date) var date
public init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
self.lenientValue = wrappedValue
}
public var wrappedValue: Wrapped? {
didSet {
if let wrappedValue {
self.lenientValue = wrappedValue
} else if oldValue != nil {
self.nilDate = self.date.now
}
}
}
public var projectedValue: Self {
get { self }
set { self = newValue }
}
var timeSinceNil: TimeInterval {
guard let nilDate else { return 0 }
return self.date.now.timeIntervalSince(nilDate)
}
} For equatability you could either compare just the extension Lenient: Equatable where Wrapped: Equatable {
public static func == (lhs: Lenient<Wrapped>, rhs: Lenient<Wrapped>) -> Bool {
lhs.wrappedValue == rhs.wrappedValue
// && lhs.lenientValue == rhs.lenientValue && lhs.nilDate == rhs.nilDate
}
} And the reducer could look something like this: extension ReducerProtocol {
public func ifLetLenient<Wrapped: ReducerProtocol>(
_ toLenientState: WritableKeyPath<State, Lenient<Wrapped.State>>,
action toWrappedAction: CasePath<Action, Wrapped.Action>,
leniency duration: TimeInterval = 1.0,
@ReducerBuilderOf<Wrapped> then wrapped: () -> Wrapped,
file: StaticString = #file, fileID: StaticString = #fileID, line: UInt = #line
) -> some ReducerProtocol<State, Action> {
let optional = self.ifLet(
toLenientState.appending(path: \.wrappedValue),
action: toWrappedAction,
then: wrapped,
file: file, fileID: fileID, line: line
)
let lenient = self.ifLet(
toLenientState.appending(path: \.lenientValue),
action: toWrappedAction,
then: wrapped,
file: file, fileID: fileID, line: line
)
return Reduce { state, action in
let lenientState = state[keyPath: toLenientState]
if lenientState.wrappedValue == nil, lenientState.timeSinceNil < duration {
return lenient.reduce(into: &state, action: action)
} else {
return optional.reduce(into: &state, action: action)
}
}
}
} I didn't include |
Beta Was this translation helpful? Give feedback.
-
Previously, reducers were static functions that could technically squirrel off some transient state to do things like:
In the
ReudcerProtocol
, reducers are constructed and run each time the store receives an action. This makes it very hard to track identities of the reducer.Beta Was this translation helpful? Give feedback.
All reactions