3

I currently have the following code

func updateItems(_ observable: Observable<ContainingEntity>) -> Observable<ContainingEntity>{
    return observable
        .concatMap({ (containingEntity) -> Observable<ContainingEntity> in
            guard let itemEntity = orderEntity.itemEntity,
                itemEntity.name.count == 0 else{
                    return Observable.just(entity)
            }

            print("Need to fetch name of item #\(itemEntity.id)")

            return RestManager.getDetailOf(item: itemEntity)
                .flatMap({ (updatedItemEntity) -> Observable<ContainingEntity> in
                    var updatedContainingEntity = containingEntity
                    containingEntity.itemEntity = updatedItemEntity
                    print("Fetched item name: \(itemEntity.name)")
                    return Observable.just(containingEntity)
                })
        })
}

Basically, I need to make sure that the itemEntity of each ContainingEntity has a name and, if not, request it with Moya.

But I'm facing the following type of output from the two prints:

  • Need to fetch name of item #1
  • Need to fetch name of item #2
  • Need to fetch name of item #3
  • Fetched item name: Name1
  • Fetched item name: Name2
  • Fetched item name: Name3

Meaning that operations in my concatMap are executed in parallel, which I don't want because of requests redundancies and some cache system I didn't show here: I can have 30 times the same item id and I don't want to request it 30 times.

What I'm expecting is:

  • Need to fetch name of item #1
  • Fetched item name: Name1
  • Need to fetch name of item #2

  • Fetched item name: Name2

  • Need to fetch name of item #3
  • Fetched item name: Name3

How can I fix this issue? Thank you very much for your help.

UPDATE:

I'm now using some kind of simple buffer, which saves ItemEntities needing to be updated and assigns these items to the next ContainingEntities with the same item identifier. This prevents Moya from performing the same request several times.

It works perfectly but I don't very much like the idea of this mechanism external to RX...

y00rn
  • 31
  • 3

1 Answers1

1

I think using a Variable or BehaviorSubject to control the sequential loading could work (code below).

However you might want to rethink the loading architecture, because by using this code, your loading times might be sub-optimal.

The concern of limiting how many simultaneous parallel requests are in-flight might belong to other component, like your networking layer.

I didn't test the code below, so it might not even compile - if it works, please let me know and accept the answer.

func updateItems(_ observable: Observable<ContainingEntity>) -> Observable<ContainingEntity>{
  let trigger = BehaviorSubject<Int>(value: 0)
  let sequential = Observable.zip(observable, trigger) { return $0.0 }
  return sequential
    .concatMap({ (containingEntity) -> Observable<ContainingEntity> in
      guard let itemEntity = orderEntity.itemEntity,
        itemEntity.name.count == 0 else{
          return Observable.just(entity)
      }

      print("Need to fetch name of item #\(itemEntity.id)")

      return RestManager.getDetailOf(item: itemEntity)
        .flatMap({ (updatedItemEntity) -> Observable<ContainingEntity> in
          var updatedContainingEntity = containingEntity
          containingEntity.itemEntity = updatedItemEntity
          print("Fetched item name: \(itemEntity.name)")
          trigger.onNext(0)
          return Observable.just(containingEntity)
        })
    })
}
Juan Carlos Méndez
  • 1,053
  • 10
  • 20
  • 1
    Thanks a lot for this very interesting answer. But I actually tried this and not only is it a bit disappointing to use such a complex mechanism (because I have the same problem in 5 managers in my app), but it also deadlocks if the Observable is empty! – y00rn Apr 25 '18 at 07:20
  • Makes sense @y00rn - I see the update to your question, about using a buffer. – Juan Carlos Méndez Apr 25 '18 at 17:21