Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift replay the last value of a completed observable

Tags:

swift

rx-swift

I have a cold observable that may get called multiple times. This observable does an expensive task (a network request) and then completes. I would like this observable to only ever make a single network call and if I need to call it again in the future I would like to get the last emitted value.

If an observable doesn't complete (i.e. just sends a next value without a completed event) I can use the .share(replay: 1, scope: .whileConnected) function to always get the last value. Unfortunately, this doesn't work with observables that completes at the end of the request. Bellow is an example:

let disposeBag = DisposeBag()
let refreshSubject = PublishSubject<Void>()

override func viewDidLoad() {
    super.viewDidLoad()

    let observable = Observable<String>.create { observer in
        let seconds = 2.0
        DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
            observer.onNext("Hello, World")
            observer.onCompleted() // <-- Works when commented out
        }

        return Disposables.create()
    }
    .share(replay: 1, scope: .whileConnected)

    refreshSubject
        .flatMap { _ in observable }
        .subscribe(onNext: { response in
            print("response: ", response)
        })
        .disposed(by: disposeBag)
}

@IBAction func refreshButtonHandler(_ sender: Any) {
    refreshSubject.onNext(())
}

Every time the refreshSubject is triggered it takes 2 seconds for the Hello, World to be printed. If I remove the observer.onCompleted() line however, it only takes 2 seconds the first time and subsequently returns a cached response.

Obviously this is just an example, in the real world I would not have any control if the observable completes or not but I would like to always just replay the last value regardless.

like image 492
nonstopcoding Avatar asked Oct 19 '25 13:10

nonstopcoding


1 Answers

So you don't want the cold observable to be re-subscribed to even when the refresh is triggered. In that case, this is the solution:

Observable.combineLatest(refreshSubject.startWith(()), yourColdObservable)
    .map { $0.1 }
    .subscribe(onNext: { val in
        print("*** val: ", val)
    })

Using flatMap means that the observable is re-subscribed to every time an event enters the flatMap. By using combineLatest instead, the cold observable will only be subscribed to once. The combineLatest operator will store the result of the observable internally and emit it again every time the refresh subject emits. (No share is needed for this method.)

like image 172
Daniel T. Avatar answered Oct 22 '25 00:10

Daniel T.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!