Best practise to use chain SignalProducers
olevabel opened this issue · comments
Hello, since all mighty StackOverflow has not been able to answer my question, I figure this is the place to get proper answer. My problem is the following:
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.
It sounds like you already know your options.
If you think about it, it makes sense: The original SignalProducer you create in doSomethingWithString
sends one value, but then does not complete and stays active this way. Why would flatMap
complete after only one value is processed?
The documentation for flatMap
states:
/// The flattened stream of values completes only when the stream of streams, and all
/// the inner streams it sent, have completed.
flatMap
processes all events from the outer stream by somehow creating an inner stream for each event. No matter what the inner streams are doing, the resulting stream stays active at least as long as the original stream is active since it has to process every event!
If you actually want to complete the whole stream after only 1 value from the original Producer is processed, then .take(first: 1)
is exactly the right thing, that states exactly your intention right in the code.
I don't know what your actual problem statement is, but if your original streams are just one value, you can use the SignalProducer.init(value:)
which immediately sends the given value and then completes, effectively doing the same as when you add the sendCompleted()
line you already found.
private func doSomethingWithString() -> SignalProducer<Int, Error> {
return SignalProducer<String, Error>(value: "Hello")
.flatMap(.latest, doSomethingWithInt(string:))
}