Wait combineLatest until #selector is called

Viewed 186

TL;DR;

I need to find out a way to setup combineLatest that processes events only after particular self.myMethod() is called without subscribing in that method.

Description

My component A has a subscribe() routin in init(), where all Rx subscriptions are set up.

import RxSwift

final class A {

    let bag = DisposeBag()

    init() {
        //...
        subscribe()
    }

    //...

    private func subscribe() {
        // Setup all Rx subscriptions here
    }

There are two other dependencies B and C, each having their statuses that A needs to combineLatest and yield some UI Event upon that combination.

Observable.combineLatest(b.status,
                         c.status)
        .filter { $0.0 == .connecting && $0.1 == .notReachable }
        .map { _ -> Error in
            return AError.noInternet
        }
        .debounce(RxTimeInterval.seconds(5), scheduler: MainScheduler.instance)
        .subscribe(onNext: { [weak self] error in
            self?.didFail(with: error)
        })
        .disposed(by: bag)

A is not a UI component and basically handles business logic, thus it should wait until UI "says" it is ready to handle that business logic. E.g., after myMethod() is called on A by UI layer.

Problem

I do want to have the Observable.combineLatest in subscribe() being setup in a way that waits until myMethod() is called and then immediately receives latest events from B's status and C's status.

Currently I do it this way in A:

public func myMethod()
    // ... 
    Observable.combineLatest(...
}

, which breaks the clean code I am striving to.

2 Answers

One thing you could do is make the publisher connectable, and call .connect() when you need to:

let publisher: Publishers.MakeConnectable<AnyPublisher<YourOutput, YourError>>

func subscribe() {
   publisher = Observable.combineLatest(b.status, c.status)
                         .filter { ... }
                         .map { ... }
                         .eraseToAnyPublisher()
                         .makeConnectable()

   publisher.subscribe(...)
}

Then, in myMethod() you can do:

func myMethod() {
   publisher.connect()
}

Another option would be to add a PublishSubject to your A class.

final class A { 
    let myMethodCalled = PublishSubject<Void>()

    init() { 
        myMethodCalled 
            .withLatestFrom(Observable.combineLatest(a.status, b.status))
            // etc...
    }

    func myMethod() {
        myMethodCalled.onNext(())
    }
}

The above might be a problem if, for example myMethod() is called before a.status and b.status emit any values though.

The best solution is to pass in an Observable that triggers the whole thing instead of calling myMethod(). Embrace the Rx paradigm and get rid of the passive (as opposed to reactive) myMethod().

Related