I've recently faced an issue with CombineLatest reporting one of it's values incorrectly. This is hard to reproduce, yet it is reproducible.
Setup:
let onFirst = CurrentValueSubject<Bool, Never>(false)
let onSecond = CurrentValueSubject<Bool, Never>(false)
onFirst
.sink { value in print("Single: first: \(value)") }
...
onSecond
.sink { value in print("Single: second: \(value)") }
...
Publishers.CombineLatest(onFirst, onSecond)
.handleEvents(receiveOutput: { print("CombineLatest: first: \($0); second: \($1)") })
...
At some point new values are published to both onFirst and onSecond. This produces following output:
2022-08-12 11:31:28.650 > will invoke onFirst.send(true)
2022-08-12 11:31:28.650 > will invoke onSecond.send(true)
2022-08-12 11:31:28.650 Single: first: true
2022-08-12 11:31:28.650 Single: second: true
2022-08-12 11:31:28.650 CombineLatest: first: true; second: false
Values are emitted only once and looking at individual subscriptions they report correct value. However combineLatest does not:
- i'd expect it would print twice (since since both values changed)
- i'd expect that final result is (true, true) since both sub publishers reported true
No idea where the combineLatest second value ('false') value comes from. Any clues? Both onFirst and onSecond is likely invoked from separate threads and combineLatest may operate on any thread as well. But as far as I understand combineLatest is thread safe (..). Since all these events occur almost simultaneously there must be some race condition? I've tried adding 'subscribe(on) and observer(on)' on the same queue but it doesn't resolve this. Am I misunderstanding something about CombineLatest?
UPDATE:
I've found this post: Swift Combine - Publishers.CombineLatest on multiple Threads
Created unit test, consider 2x cases:
- no synchronization
Publishers.CombineLatest(onFirst, onSecond)
--------- Post
post first: true
post second: true
received: true true
received: true false
- synchronized
Publishers.CombineLatest(first.receive(on: queue), second.receive(on: queue))
--------- Post
post first: false
post second: false
received: false true
received: false false
I thought CombineLatest is thread safe (i.e. does not matter which thread emits values, CombineLatest serializes processing). This seems not true. As consequence, CombineLatest either does not emit all changes, or emits incorrect values. I would appreciate someone clarifying...