0

I have the following pipeline setup, and for some reason I can't understand, the second flatMap is skipped:

func letsDoThis() -> SignalProducer<(), MyError> {

    let logError: (MyError) -> Void = { error in
        print("Error: \(error); \((error as NSError).userInfo)")
    }

    return upload(uploads) // returns: SignalProducer<Signal<(), MyError>.Event, Never>
        .collect() // SignalProducer<[Signal<(), MyError>.Event], Never>
        .flatMap(.merge, { [uploadContext] values -> SignalProducer<[Signal<(), MyError>.Event], MyError> in
            return context.saveSignal() // SignalProducer<(), NSError>
                .map { values } // SignalProducer<[Signal<(), MyError>.Event], NSError>
                .mapError { MyError.saveFailed(error: $0) } // SignalProducer<[Signal<(), MyError>.Event], MyError>
        })
        .flatMap(.merge, { values -> SignalProducer<(), MyError> in
            if let error = values.first(where: { $0.error != nil })?.error {
                return SignalProducer(error: error)
            } else {
                return SignalProducer(value: ())
            }
        })
        .on(failed: logError)
}

See the transformations/signatures starting with the upload method. When I say skipped I mean even if I add breakpoints or log statements, they are not executed.

Any idea how to debug this or how to fix?

Thanks.

EDIT: it is most likely has something to do with the map withing the first flatMap, but not sure how to fix it yet. See this link.

EDIT 2: versions

- ReactiveCocoa (10.1.0):
- ReactiveObjC (3.1.1)
- ReactiveObjCBridge (6.0.0):
- ReactiveSwift (6.1.0)

EDIT 3: I found the problem which was due to my method saveSignal sending sendCompleted.

extension NSManagedObjectContext {
 func saveSignal() -> SignalProducer<(), NSError> {
    return SignalProducer { observer, disposable in
        self.perform {
            do {
                try self.save()
                observer.sendCompleted()
            }
            catch {
                observer.send(error: error as NSError)
            }
        }
    }
}

Sending completed make sense, so I can't change that. Any way to change the flatMap to still do what I intended to do?

Zsolt
  • 3,648
  • 3
  • 32
  • 47
  • Can you explain in more detail what you are trying to do? Do you want to wait until all uploads have completed before saving the managed object context, or do you want to save each time an upload finishes? Do you still want to save if the upload fails? – jjoelson Nov 17 '20 at 20:44
  • 1
    thanks for the answer @jjoelson, I want to save after all uploads have completed, even if some of them failed. – Zsolt Nov 18 '20 at 13:55

1 Answers1

0

I think the reason your second flatMap is never executed is that saveSignal never sends a value; it just finishes with a completed event or an error event. That means map will never be called, and no values will ever be passed to your second flatMap. You can fix it by doing something like this:

context.saveSignal()
    .mapError { MyError.saveFailed(error: $0) }
    .then(SignalProducer(value: values))

Instead of using map (which does nothing because there are no values to map), you just create a new producer that sends the values after saveSignal completes successfully.

jjoelson
  • 5,771
  • 5
  • 31
  • 51
  • Does the flatten strategy for these two flatMaps makes any difference in this context? Latest, merge, concat seem to make no difference. How can I reason about which one to pick? (At this point after collect this might be synchronous and does not matter?) – Zsolt Nov 18 '20 at 13:58
  • That's correct, in this context they are all the same because `collect` only sends a single value to `flatMap`. The strategy tells `flatMap` how to handle multiple values. I wrote an answer last year that explains the different strategies: https://stackoverflow.com/a/58615144/642233 – jjoelson Nov 18 '20 at 14:05