pointfreeco/swift-composable-architecture

Non-main thread warning when calling using .refreshable from child @ViewBuilder property

Closed this issue · 1 comments

Description

If you add the .refreshable modifier to the child property in @ViewBuilder, attempting to refresh the list triggers a warning that the call was made on a non-main thread:

"Store.send" was called on a non-main thread with: Test.Action.refresh …

The "Store" class is not thread-safe, and so all interactions with an instance of "Store" (including all of its scopes and derived view stores) must be done on the main thread.

received action:
Test.Action.refresh
(No state changes)

received action:
Test.Action.increment
Test.State(

  • _count: 0
  • _count: 1
    )

Example:

struct TestView: View {
    let store: StoreOf<Test>
    
    var body: some View {
        NavigationStack {
            list
        }
    }
    
    @ViewBuilder
    var list: some View {
        List {
            Text(verbatim: "Count: \(store.count)")
        }
        .refreshable {
            await store.send(.refresh).finish()
        }
    }
}

However, if you place the list directly in the body, everything works as expected without warnings:

received action:
Test.Action.refresh
(No state changes)

received action:
Test.Action.increment
Test.State(

  • _count: 0
  • _count: 1
    )

Example:

struct TestView: View {
    let store: StoreOf<Test>
    
    var body: some View {
        NavigationStack {
            List {
                Text(verbatim: "Count: \(store.count)")
            }
            .refreshable {
                await store.send(.refresh).finish()
            }
        }
    }
}

Checklist

  • I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

No non-main thread warning

Actual behavior

Store.send" was called on a non-main thread with: Test.Action.refresh …

The "Store" class is not thread-safe, and so all interactions with an instance of "Store" (including all of its scopes and derived view stores) must be done on the main thread.

Steps to reproduce

No response

The Composable Architecture version information

1.10.3

Destination operating system

iOS 17.4

Xcode version information

Version 15.3 (15E204a)

Swift Compiler version information

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0

Hi @uniqby, this is just how Swift/SwiftUI behaves, for better or worse. The body of views is marked as @MainActor(unsafe), and so everything inside inherits @MainActor, including refreshable. But as soon as you move some of the view out to another computed property, it loses the @MainActor. You can either mark the list property as @MainActor, or you can use refreshable { @MainActor in … }.

Since this is not an issue with the library I am going to convert it to a discussion. Feel to free to continue the conversation over there.