Here are the three points I want to achieve:
- I want to emit a specific value in a random interval
- I want to emit only if upstream has a specific value
- I want to cancel the random time interval and re-start it if there is a new value upstream
To put it more into context:
I want to toggle a state on tap from A to B, B to C and then back C to A. However, if the current state is B and there is no user input, I want to toggle the state to C after a random time interval. If the current state is B and the user taps the screen, I do not want to toggle the state to C anymore, with the exception if the user toggled often enough to be on B again, in which case I would want a new random interval to be used.
I have some (more or less) pseudo code to clarify my problem further.
class ViewModel {
var state: State
private var stateSubject: CurrentValueSubject<State, Never>
private var cancellables: Set<AnyCancellable> = []
init(initialState: State) {
state = initialState
stateSubject = CurrentValueSubject(state)
// Pipe to store new state
stateSubject
.sink { [unowned self] in
self.state = $0
}
.store(in: &cancellables)
// Logic to switch specific state after interval
// Approach 1
stateSubject
.debounce(for: .seconds(randomInterval), scheduler: RunLoop.main)
.filter { $0 == .B }
.sink { [unowned self] _ in
stateSubject.send(.C)
}
.store(in: &cancellables)
// --------------------------------
// Approach 2
stateSubject
.flatMap(maxPublishers: .max(1)) { state in
Future { [unowned self] promise in
DispatchQueue.main.asyncAfter(deadline: .now() + randomInterval) {
promise(.success(state))
}
}
}
.filter { $0 == .B }
.sink { [unowned self] _ in
stateSubject.send(.C)
}
.store(in: &cancellables)
}
var randomInterval: Double { Double.random(in: 1...4) }
// Called on tap
func toggleState() {
state = state.toggle()
}
}
extension ViewModel {
enum State {
case A, B, C
func toggle() -> Self {
switch self {
case .A:
return .B
case .B:
return .C
case .C:
return .A
}
}
}
}
Approach 1:
This seems reasonable, however, as the .debounce
operator is only created once and therefore only accesses randomInterval
once, resulting in always debouncing the same amount of time.
Approach 2: This works fairly well, except if the user is toggling the state a little too often. I could not find the cause of the issue, but found the state toggling even on State.A
Thank you for your help and let me know if you have any further questions.