Reputation: 2957
In RxSwift, a flatMap operator can easily return a non-completing Observable. Let's say we have this (contrived and silly) Observable chain:
let repo = DataRepository()
Observable
.just(Int.random(in: 0 ..< 1000))
.flatMap { num -> Observable<String> in
if num == 42 {
return .never()
}
return repo
.fetchData()
.filter { $0.statusCode == 200 }
.map { $0.data.title }
}
With Combine, the closest I can get is something like this (haven't tried to compile, but you get the idea):
Just(Int.random(in: 0 ..< 1000))
.flatMap { num -> AnyPublisher<String, Never> in
if num == 42 {
return Empty<String, Never>(completeImmediately: false).eraseToAnyPublisher()
}
return repo
.fetchData()
.filter { $0.statusCode == 200 }
.map { $0.data.title }
.eraseToAnyPublisher()
}
I'm okay-ish with this solution, but I see two problems that I would like to avoid:
1) The Combine solution is somewhat more verbose to achieve the same thing.
2) I have to call eraseToAnyPublisher()
on both returned Publishers, else the return types don't match. I believe calling eraseToAnyPublisher()
prevents Swift from applying some internal optimizations (I can't find the article I read about this optimization anymore; the information is scarce around this)
Does anyone have a better approach to handling this example scenario?
Upvotes: 0
Views: 3660
Reputation: 6557
The previous answer does not answer the original question, it only gives suggestions on how to avoid it.
But there are legitimate situations where this behavior is needed, such as a Timer, that you don't want to tick and consume resources.
The code in the question is almost correct, but it needs to be:
Just(Int.random(in: 0 ..< 1000))
.map { num -> AnyPublisher<String, Never> in
if num == 42 {
return Empty<String, Never>(completeImmediately: false).eraseToAnyPublisher()
}
return repo
.fetchData()
.filter { $0.statusCode == 200 }
.map { $0.data.title }
.eraseToAnyPublisher()
}
.switchToLatest()
A more practical use case, for example a timer that ticks every second, and can be paused.
let isPaused: AnyPublisher<Bool, Never>
let pausableTimer = isPaused.
.map { isPaused in
if isPaused {
return Empty<Void, Never>()
.eraseToAnyPublisher()
} else {
return Timer.publish(every: 1, on: .main, in: .default)
.autoconnect()
.map { _ in }
.eraseToAnyPublisher()
}
}
.switchToLatest()
You don't want to use someting like CombineLatest
, because the Timer would keep on ticking, while it's paused and the output even is ignored.
Upvotes: 0
Reputation: 2193
Try to lift any conditional logic into operators.
Conditions under which you emit something like Observable.never
are best captured in a filter, that way you get the "never" behavior for free.
Example:
Just(Int.random(in: 0 ..< 1000))
.filter { $0 != 42 }
.flatMap {
return repo
.fetchData()
.filter { $0.statusCode == 200 }
.map { $0.data.title }
}
I don't know enough about the types in DataRepository
to know if you need to type erase inside the flatMap
closure.
Upvotes: 1