Search code examples
swiftflatmapreactive-swift

ReactiveSwift pipeline flatMap body transform not executed


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?


Solution

  • 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.