3

In my OS X status bar app I'm using interval function to periodically call an external api and display the result:

Observable<Int>
    .interval(120.0, scheduler: MainScheduler.instance)
    .startWith(-1) // to start immediately
    .flatMapLatest(makeRequest) // makeRequest is (dummy: Int) -> Observable<SummaryResponse?>
    .subscribeNext(setSummary)
    .addDisposableTo(disposeBag)

However, if user changes the preferences in the meantime, I would like to "restart" this interval and make a new call immediately to reflect the changes (without having to wait for the next call).

What's the best way to do this?

  1. Store the observable as a property and set it to nil or call .dispose() on it (or both) and create a new observable ?
  2. Set disposeBag to nil and create a new observable ?
  3. Any other way?
Maciej Wozniak
  • 1,174
  • 15
  • 27

1 Answers1

7

What you're looking for is merge. You have two Observables, one of which is an interval and the other which represents preference changes. You want to merge those into one Observable with the elements from both, immediately as they come.

That would look like this:

// this should really come from somewhere else in your app
let preferencesChanged = PublishSubject<Void>()

// the `map` is so that the element type goes from `Int` to `Void`
// since the `merge` requires that the element types match
let timer = Observable<Int>.timer(0, period: 3, scheduler: MainScheduler.instance).map { _ in () }

Observable.of(timer, preferencesChanged)
    .merge()
    .flatMapLatest(makeRequest)
    .subscribeNext(setSummary)
    .addDisposableTo(disposeBag)

Also notice how I'm using timer instead of interval, since it allows us to specify when to fire for the first time, as well as the period for subsequent firings. That way, you don't need a startWith. However, both ways work. It's a matter of preference.

One more thing to note. This is outside of the scope of your question (and maybe you kept it simple for the sake of the question) but instead of subscribeNext(setSummary), you should consider keeping the result as an Observable and instead bindTo or drive the UI or DB (or whatever "summary" is).

solidcell
  • 7,639
  • 4
  • 40
  • 59
  • Thanks for the answer and all the suggestions! Changing to `timer` and mapping the dummy int to void made this code look much better :). `setSummary` basically sets `NSStatusItem.title`, will check if I can employ Rx to deal with it :). – Maciej Wozniak May 25 '16 at 20:47
  • 1
    But this doesn't really reset anything, does it? it just emits events when the timer is ready OR the other observable emits something, correct? I"d still be interested in a good way to truly reset the interval if that is true. – Doug Sjoquist Jul 15 '16 at 21:33
  • I don't understand what you mean. We aren't resetting the timer Observable. Rather, that timer Observable is being used to reset the Observable returned by makeRequest. It sounds like you're wanting something other than what this question asked for. – solidcell Jul 16 '16 at 09:46