Example:
struct TestState {
var isEnabled = false
}
enum TestError: Swift.Error {
case notEnabled
}
class TestController: ObservableObject {
@Published var state: TestState = .init()
@Published var next: Result<Int, TestError> = .failure(.notEnabled)
}
What I want to achieve:
- If
TestController.state.isEnabled
isfalse
, the publisherTestController.$next
will always emit aResult.failure(TestError.notEnabled)
value to its subscribers. - If
TestController.state.isEnabled
istrue
, the publisherTestController.$next
will emit a randomizedInt
value wrapped inResult.success
type to its subscribers.
What I did:
-
An
Int
publisher that wrapped in aDeferred
to take advantage of its laziness so that each subscription generate a new value that is different from the previous one:let randomizer = Deferred<Just<Int>> { .init(.random(in: 0..<10)) } let r1 = randomizer.sink { print("1st:", $0) } let r2 = randomizer.sink { print("2nd:", $0) } // running result // 1st: 8 // 2nd: 6
-
Subscribe the
TestController.$state
inTestController.init
to swap the publishing source ofTestController.$next
class TestController: ObservableObject { @Published var state: TestState = .init() @Published var next: Result<Int, TestError> = .failure(.notEnabled) private var cancellables: Set<AnyCancellable> = [] init<P: Publisher>( randomizer: Deferred<P> ) where P.Output == Int, P.Failure == Never { self.$state .map(\.isEnabled) .removeDuplicates() -> .combineLatest(randomizer) .sink { [weak self] isEnabled, value in self.map { $0.next = isEnabled ? .success(value) : .failure(.notEnabled) } } .store(in: &self.cancellables) } }
-
With this implementation, the
TestController.$next
always publishes a same value whenTestController.state.isEnabled
istrue
, which I assume expected since there is only one subscription to therandomizer
:let controller = TestController(randomizer: randomizer) let cancellable = controller.$next .sink { value in print("Result:", value) } DispatchQueue.global().asyncAfter(deadline: .now() + 1) { controller.state.isEnabled = true } DispatchQueue.global().asyncAfter(deadline: .now() + 2) { controller.state.isEnabled = false } DispatchQueue.global().asyncAfter(deadline: .now() + 3) { controller.state.isEnabled = true } // running result // Result: failure(__lldb_expr_121.TestError.notEnabled) // Result: success(6) // Result: failure(__lldb_expr_121.TestError.notEnabled) // Result: success(6)
Other Attempt Made:
-
I have made another implementation:
class TestController: ObservableObject { @Published var state: TestState = .init() @Published var next: Result<Int, TestError> = .failure(.notEnabled) private var cancellables: Set<AnyCancellable> = [] init<P: Publisher>( randomizer: Deferred<P> ) where P.Output == Int, P.Failure == Never { self.$state .map(\.isEnabled) .removeDuplicates() .sink { [weak self] isEnabled in guard isEnabled else { self?.next = .failure(.notEnabled) return } randomizer.sink { value in self?.next = .success(value) } } .store(in: &self.cancellables) } }
-
it does give me the result I want:
// running result // Result: failure(__lldb_expr_121.TestError.notEnabled) // Result: success(2) // Result: failure(__lldb_expr_121.TestError.notEnabled) // Result: success(8)
-
But the nested
.sink
looks ugly.So I am wondering, is there an operator or another way of implementation that provides the combination and keep the laziness of the
Deferred
at the same time?
The real life scenario
As you may have suspected, the randomizer
of type Deferred<Just<Int>>
is not what I am dealing with in real life, rather, I am given an API looks like this:
func load() -> Future<Int, Never>
To simplify the question, I used the randomizer
to describe the problem.
Please help, thanks!