0

since Im new to the reactive programming, I have somewhat beginner question about chaining SignalProducers in ReactiveSwift. My goal is to create a chain of SignalProducers, to test one of my flows. Doing the SignalProducer chaining I stumbled upon an obstacle. If my inner-most SignalProducer is calling sendCompleted it will not be propagated downstream. Here is a mock code in Playground

  private func doSomethingWithString() -> SignalProducer<Int, Error> {
  return SignalProducer<String, Error> {observer, _ in
    observer.send(value: "Hello")
  }.flatMap(.latest, doSomethingWithInt(string:))
}

private func doSomethingWithInt(string: String) -> SignalProducer<Int, Error> {
  return SignalProducer<Int, Error> { observer, _ in
    observer.send(value: 2)
    observer.sendCompleted()
  }
}

func test() -> SignalProducer<Int,Error> {
  return doSomethingWithString()

}
func leGo() {

  test().start { (event) in
    switch event {
    case .completed:
      print("DONE")
    case .value(let value):
      print("WE HAVE A VALUE: \(value)")
    case.interrupted:
      print("INTERRUPTED")
    case .failed(let error):
      print("FAILED \(error)")
    }
  }
}

leGo()

In this snippet the value is printed as expected "WE HAVE A VALUE 2", but completed is never executed and thus "DONE" is never printed. I would highly appreciate if someone reasons about why is it so and how to properly do it. I can make it work by calling sendCompleted() in doSomethingWithString, but since my original stream has more than 2 methods, I do not want to write it in each of the methods. Also .take(first:1) is an option that sounds really weird to me, because I really do not want to go through all of the chain to take 1 item. Rather I would like to terminate the whole stream in one place in one completion if possible.

Ollikas
  • 199
  • 1
  • 3
  • 14

1 Answers1

2

The purpose of flatMap is to convert each incoming value into a new producer and then flatten the values from those inner producers into a single stream according to some strategy. It doesn't send a completed event when an inner producer completes for the simple reason that you can have multiple inner producers, and you would miss the values from all of the other inner producers if it completed when one of its inner producers completed.

So the answer is that your outer producer must send a completed event if you want the overall stream to complete.

Here's an example of how flatMap is commonly used that hopefully illustrates why flatMap works this way:

let searchResults = searchText
    .flatMap(.latest) { text in
        performSearch(for: text)
    }

The searchResults signal is meant to update with the new results every time the user changes the search text. You therefore would not want the stream to complete just because one of the searches completed; you want to kick off a new search and update the results when the user changes the text.

You could write some kind of helper initializer for SignalProducer that uses take(first: 1) to automatically complete after a single value, but it is more idiomatic to have all of your producers just properly send the completion event when are done.

jjoelson
  • 5,771
  • 5
  • 31
  • 51