1

In my Swift UIViewController, I'm attempting to subscribe to a class member of type Variable, run it through a flatMapLatest call, and then have the onCompleted() call in the flatMapLatest observable execute on all subscribers. However, while onNext() is called, onCompleted() never is and I'm not sure why.

My class member is defined as:

private let privateVar = Variable<String>("")

while in my viewDidLoad() method, I set up the observables:

let localVar = self.privateVar.asObservable().distinctUntilChanged()

localVar.subscribe(onNext: { [weak self] sent in print("first onNext called") })
        .disposed(by: self.disposeBag)

let mappedVar = localVar.flatMapLatest { self.ajaxLoad(var1: $0) }.share()

mappedVar.subscribe(
  onNext: { [weak self] queryRes in
    print("onNext called!")
  },
  onCompleted: { [weak self] in
    print("onCompleted called!")
  }
)
.disposed(by: self.disposeBag)

and my ajaxLoad method:

func ajaxLoad(var1 myVar: String) -> Observable<QueryResponse> {
  return Observable.create { observable in
    apollo.fetch(query: MyQuery()) { (result, _) in
      observable.onNext(result?.data?.myQuery)
      observable.onCompleted()
    }

    return Disposables.create()
  }
}

I'm fairly new to ReactiveX so I may be a little hazy on what the Rx lifecycle actually looks like. Why might onNext be called in the flatMapLatest call, but not onCompleted? Any help would be appreciated. Thanks in advance!

Nickersoft
  • 684
  • 1
  • 12
  • 30

2 Answers2

2

The flatMap operator does not emit completed events of any observable that you return inside the block.

The following code illustrates this clearly. .just(_) emits the element and then a completed event, which does not terminate to subscription.

_ = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
    .debug("before flatmap")
    .flatMap { .just($0 * 2) }
    .debug("after flatmap")
    .subscribe()

In fact, Variable only emits completed when deallocated. See source v4.0. Note that Variable is deprecated in RxSwift 4, you are encouraged to use RxCocoa's similar BehaviorRelay instead.

deinit {
    _subject.on(.completed)
}
CloakedEddy
  • 1,965
  • 15
  • 27
  • 1
    But note that an unhandled error in the inner sequence will terminate the outer sequence (`flatMap { .error(SomeError("an error")) }`) – Valérian Aug 10 '18 at 07:40
1

Since you said you are new and a "little hazy"...

Keep in mind that whenever localVar changes, it emits a new value and ajaxLoad(var1:) gets called. The result of ajaxLoad(var1:) then gets pushed to your subscribe's onNext closure.

Also keep in mind that if an Observable emits a .completed it's dead. It can no longer emit anything else.

So flatMapLatest can't complete (unless its source completes.) If it did, it would kill the whole pipe and no more changes to localVar would get routed through the pipe, ajaxLoad(var:1) wouldn't get called again with the new value and nothing more would get pushed to the subscribe's onNext method.

A sequence of observables can be thought of like a Rube Goldberg machine where a completed shuts down the machine and an error breaks it. The only time you should shut down the machine is if the source (in this case localVar) is finished emitting values or the sink (destination, in this case the onNext: closure) doesn't want any more values.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72