ViewStore sometimes replays previous state values to subscribers #698
Replies: 6 comments 6 replies
-
Thanks for providing a good example. I can confirm the same result on my end and am trying to get a better idea of the issue. Is there a particular reason you're not putting the update country logic inside the reducer? My guess is that sending .updateCountry via sink is causing a race condition. |
Beta Was this translation helpful? Give feedback.
-
@iampatbrown note that if you also subscribe to the UIKit / AppKit apps tend to use publisher observation a lot and this situations is likely to happen in bigger apps, causes weird to track down bugs. |
Beta Was this translation helpful? Give feedback.
-
@krzysztofzablocki this is more to do with how combine works though right? You would probably have to send the action using func testCombine() {
var cancellables: Set<AnyCancellable> = []
struct State: Equatable {
var city: String
var country: String
}
let state = CurrentValueSubject<State, Never>(.init(city: "New York", country: "USA"))
state.map(\.city)
.removeDuplicates()
.filter { $0 == "London" }
.sink { _ in state.value.country = "UK" }
.store(in: &cancellables)
var seenCountries = [String]()
state.map(\.country)
.removeDuplicates()
.sink { seenCountries.append($0) }
.store(in: &cancellables)
state.value.city = "London"
XCTAssertEqual(seenCountries, ["USA", "UK"])
} |
Beta Was this translation helpful? Give feedback.
-
when I was debugging TCA internals I didn't really notice anything wrong or inconsistent between failure / success cases on the side of library itself, so definitely feels like combine thing. If there is no workaround for this kind of scenario probably worth adding a note in doc / sample |
Beta Was this translation helpful? Give feedback.
-
There is some non-determinism in Combine that you are seeing here. It seems that the order of subscriptions isn't guaranteed. Running this in a playground you will see that sometimes it crashes and sometimes it doesn't: var cancellables: Set<AnyCancellable> = []
let subject = CurrentValueSubject<Int, Never>(1)
var value = 0
subject.sink { value = $0 }
.store(in: &cancellables)
subject.sink { assert($0 == value) }
.store(in: &cancellables)
subject.send(1) For this reason you probably should only do truly independent tasks inside |
Beta Was this translation helpful? Give feedback.
-
I played with this a little yesterday, and it looks like a small change could make this a little more deterministic.
Haven't thought much about any other effects this might introduce though.. :-/ |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Describe the bug
ViewStore
publisher appears to sometimes replay its previous state value after publishing the latest, correct value.To Reproduce
The scenario that produces this behavior is:
ViewStore
action which updates "Property A" on state..send
above, synchronously dispatch another action which mutates "Property B".Sometimes, the "Property B" subscriber receives a third, final update with the store's initial value for "Property B", after having received the value set in step 2.
The bug only repros ~50% of the time. The following test may need to run a number of times before this repros:
TcaWithTestCase.zip
Expected behavior
ViewStore
shouldn't replay previous state valueScreenshots
n/a
Environment
Beta Was this translation helpful? Give feedback.
All reactions