0

i have a question about ReactiveCocoa (v5) with Swift 3. In my project I have different services. For example the api or for saving or getting stuff from disk. Those services are returning SignalProducer. I have now something like a sequence of calling different services which are reliant on each other. Here the sequence:

  1. calling service to get key
  2. calling api with service by using key
  3. getting data form api (returns multiple models)
  4. save first model with first-model-service
  5. save second model with second-model-service

In my sequence I have to pass also data from the second service call down to the model saving.

keyService.get().flatMap(.latest) { (key) -> SignalProducer<[Data],Error> in

    return self.dataService.get()(key: key)
}
.flatMap(.latest) { (data) -> SignalProducer<Bool, Error> in

    return self.firstModelService.save(data["Model1"])
}
.flatMap(.latest) { (data) -> SignalProducer<Bool, Error> in

    //how to get data here?

    return self.secondModelService.save(data["Model2"])
}.startWithFailed({ e in

})

Furthermore is it possible that if one of the SignalProducer along the sequence is sending an error that the complete sequence will stop and a callback is called with the error as the parameter. I don't know if startWithFailed is the correct function here.

Does someone has a similar scenario like mine and can give me an example? I read through some tutorials but I can't figure it out.

Yetispapa
  • 2,174
  • 2
  • 30
  • 52
  • Does it not work as expected? On first glance, it looks fine to me and `startWithFailed` should also be the correct operator. If any of the SignalProducers sends an error, the error should be propagated through the whole chain to the `startWithFailed` observer. – MeXx Jun 05 '17 at 12:48
  • Yeah, this looks correct to me if you only care about handling the failure case and there's nothing else to do on success. – jjoelson Jun 05 '17 at 13:32
  • Ok there's also one other thing. Do you see the comment in the last flatMap? How do I get the data from the api request there? – Yetispapa Jun 05 '17 at 16:14
  • 1
    I have some confusion with the types here. Is `data` in the first `flatMap` supposed to be an array of `Data` values or is it supposed to be flattened into individual `Data` values? If you just want to pass `data` along to the next `flatMap`, then use `map`: `return self.firstModelService.save(data["Model1"]).map { _ in data }` (you'll have to update your type annotations). – jjoelson Jun 05 '17 at 16:40

1 Answers1

1

You are correct in saying that if any of the SignalProducers along the sequence fail, none of the following blocks will execute and the execution will skip directly to the block in startWithFailed.

If the keyService fails, we cannot use the dataService, and if the dataService fails, we cannot save either model, so this behavior makes sense for these two operations.

However, the modelService operations do not depend on each other to finish and therefore should execute concurrently in our signal model. To execute in sequence, you already know to use flatMap- it is just as easy to execute concurrently using zip or combineLatest (see documentation). By executing the model service operations concurrently with zip/combineLatest, we also get data in the correct scope for free.

Using combineLatest, your code becomes:

keyService.get().flatMap(.latest) { (key) -> SignalProducer<[Data],Error> in
  return self.dataService.get()(key: key)
}.flatMap(.latest) { (data) -> SignalProducer<(Bool, Bool), Error> in
  return SignalProducer.combineLatest(
    self.firstModelService.save(data["Model1"]),
    self.secondModelService.save(data["Model2"])
  )
}

And you can see the resulting signal producer now has Value type of (Bool, Bool), indicating respectively the results of modelService1.save and modelService2.save

Re: startWithFailed

When you start your signal producer, the function you choose to start it with will define what events are handled by your code in the callback block. If you start the SignalProducer with startWithFailed then only errors will result in your completion block being called.

The best option for operations like this is usually startWithResult which passes a Result<Value, Error> into your completion block and calls your completion block on every .value or .failed event.

However, if you want to take no action on a successful fetch & save and only need to handle errors, then startWithFailed is indeed the correct choice.

Community
  • 1
  • 1
Evan Drewry
  • 794
  • 6
  • 17