Combine `.receive(on: RunLoop.main)` loses sent value. How can I make it work?

Hi @somu,

What version of iOS are you testing on?

@Tony_Parker

Following are the versions:
iPhone: iOS 13.2.3 (17B111)
iOS Simulator: iOS 13.2.2

Are you able to test using the new behavior on iOS 13.3 (beta)?

1 Like

Oops my bad, I haven't tested it on iOS 13.3 (beta), will test it and post the results.

@Tony_Parker @clayellis, sorry about the confusion, I have tested using Xcode Beta 11.3 beta (11C24b) using the iOS 13.3 simulator and the values to the subscriber are coming through while using receive(on: RunLoop.main) as expected.

Note: I am sorry I don't have a test device to test so couldn't test on a real device but simulator works as expected.

Simulator testing should be fine too! Thanks for verifying.

1 Like

Thanks a lot for the quick response.

I am experiencing the same issue. Specifically we are wanting to run a publisher that performs an expensive operation on a background thread while receiving the results on the main queue. It appears to be a bug in the framework but any workarounds are appreciated! (edit: read too fast and missed that this might be fixed in the Xcode 11.3 beta—will check there—thanks!)

Followup: I tested this against Xcode 11.3 beta 1 in Playgrounds and the issue is still present. Workarounds/fixes appreciated!

let a = PassthroughSubject<Int, Never>()
let b = PassthroughSubject<String, Never>()

let c = a
    .map { a -> Int in
        RunLoop.current.run(until: Date().addingTimeInterval(1))
        return a
    }
    .subscribe(on: DispatchQueue.global())
    .receive(on: DispatchQueue.main)
    
let cancellable = Publishers.CombineLatest(b, c).sink {
    print("b, c", $0, $1)
}

a.send(3)
b.send("3")
1 Like

subscribe(on:) is now the one operator which does send the subscription asynchronously.

Hello, what is "now"? Does the Combine behavior change from one OS version to another? Is there some release notes that could help us developers adapt our Combine code depending on the OS version?

"now" is: as of iOS 13.3 (as noted above).

I'm working on the release note issue.

1 Like

Thank you! When we write apps or libraries, it's important to be aware of the various Combine flavors!

1 Like

You folks have been talking about iOS. How does all of this relate to macOS?

2 Likes

The change is present on all the aligned software updates for all platforms. iOS 13.3, watchOS 6.1.1, tvOS 13.3, and macOS 10.15.2. (Hope I got all those version numbers right).

the subscribe(on:) is still async on iOS 13.3.1 is there a workaround to reliably deliver the value downstream to sink ? using buffer in this case on iOS 13.2 works well but on iOS 13.3.1 doesn't work and the value doesn't get delivered at all times. And without using buffer: on iOS 13.2: sometimes the value got delivered and sometimes didn't deliver.
iOS 13.3.1: the value didn't get delivered all the times.

This issue (FB7298518) has been fixed in macOS 10.15.5 19F101. Failures are now correctly delayed.

2 Likes

I have similar problem now
Simulator iOS 16.2
Xcode Version 14.2 (14C18)
Mac OS 13.3 (22E252)

Example to reproduce:

var subscriptions: Set<AnyCancellable> = []

func test_CombineLatest() throws {

        let expectation = self.expectation(description: "Async call")

        let deferredPublisher = Deferred {
            Future<String, Never> { promise in
                Swift.print("promise A")
                promise(.success("A"))
            }
        }

        let publisher1 = deferredPublisher
            .print("publisher1")
            .subscribe(on: DispatchQueue.global())
            .eraseToAnyPublisher()

        let publisher2 = PassthroughSubject<String, Never>()

        Publishers.CombineLatest(
            publisher1,
            publisher2.eraseToAnyPublisher()
        )
        .print("CombineLatest")
        .sink { value in
            expectation.fulfill()
            Swift.print("CombineLatest: ", value)
        }
        .store(in: &subscriptions)

        publisher2.send("B")

        wait(for: [expectation], timeout: 4)
    }

promise call before receive subscription
Sometimes it works