Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stale State in Connected Views #18

Open
danhalliday opened this issue Apr 23, 2021 · 3 comments
Open

Stale State in Connected Views #18

danhalliday opened this issue Apr 23, 2021 · 3 comments

Comments

@danhalliday
Copy link

danhalliday commented Apr 23, 2021

I’m seeing stale states sometimes in connected views. Can’t figure it out — have removed all middleware. It looks like after an action is processed and the state is updated, map(state:dispatch:) is called with the old state. But only sometimes (perhaps 20% of the time).

func map(state: AppState, dispatch: @escaping DispatchFunction) -> Props {
    print("Latest screen:", state.diaryState.currentScreen) // Sometimes currentScreen doesn't update

I would happily dig into this or submit a PR but I’m stumped. The Store implementation looks straightforward. I can’t produce a reduced test case but at the same time there’s nothing unusual about the project I’m seeing the issue in.

Adding in an additional DispatchQueue.main.async {} into a new middleware fixes it most of the time but not always. I guess it has to be something to do with how SwiftUI is coalescing updates to the @Published public var state: StoreState store property.

@danhalliday
Copy link
Author

danhalliday commented Apr 24, 2021

This does in fact seem to be down to SwiftUI’s coalescing of updates. Getting rid of the following async dispatch in the Store fixes the problem:

public func dispatch(action: Action) {
    // DispatchQueue.main.async {
        self.dispatchFunction(action)
    // }
}

During an action update, I see the StoreConnector body getter called 3 times, but the map(state:dispatch) function only called once. With the Store’s DispatchQueue.main.async present, the order of the body getter and map function jump around and sometimes the map function is called earlier than the body getter call showing the latest state. When I remove the async dispatch, the calls are in exactly the same order every time with the map function always following the StoreConnector body getter call that has received the new state.

Still not exactly sure on the root cause but the fix seems reliable. Also FWIW my view navigation feels snappier after removing the async dispatch. Perhaps previously that was causing SwiftUI’s change coalescing mechanism or its animation system to thrash?

@danhalliday
Copy link
Author

danhalliday commented Apr 24, 2021

@Dimillian was the rationale behind the DispatchQueue.main.async call to be a 'just in case' users dispatch actions on threads other than the main thread?

Flagging the Store as dirty with the objectWillChange publisher in the dispatch block also fixes the issue (again just FWIW, I’m meddling in things I don’t fully understand!)

public func dispatch(action: Action) {
    DispatchQueue.main.async {
        self.objectWillChange.send()
        self.dispatchFunction(action)
    }
}

@xinhuang327
Copy link

I have the same problem but cannot figure out exactly why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants