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

Isolate state and send to main actor #35

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Sources/ObservableStore/ObservableStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ public struct Update<Model: ModelProtocol> {
public protocol StoreProtocol {
associatedtype Model: ModelProtocol

var state: Model { get }
@MainActor var state: Model { get }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get-only property should be nonisolated.


func send(_ action: Model.Action) -> Void
@MainActor func send(_ action: Model.Action) -> Void
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than isolating send to main actor, we may want to allow send to be nonisolated, and have a separate main-actor isolated transact method which does the actual transaction. This would give us more flexibility in where we can call send, while ensuring state changes only happen on main.

}

/// Store is a source of truth for a state.
Expand All @@ -172,6 +172,7 @@ public protocol StoreProtocol {
/// Store has a `@Published` `state` (typically a struct).
/// All updates and effects to this state happen through actions
/// sent to `store.send`.
@MainActor
public final class Store<Model>: ObservableObject, StoreProtocol
where Model: ModelProtocol
{
Expand Down Expand Up @@ -312,6 +313,7 @@ where Model: ModelProtocol
}
}

@MainActor
public struct ViewStore<ViewModel: ModelProtocol>: StoreProtocol {
private var _send: (ViewModel.Action) -> Void
public var state: ViewModel
Expand Down Expand Up @@ -344,6 +346,7 @@ extension ViewStore {

extension StoreProtocol {
/// Create a viewStore from a StoreProtocol
@MainActor
public func viewStore<ViewModel: ModelProtocol>(
get: (Self.Model) -> ViewModel,
tag: @escaping (ViewModel.Action) -> Self.Model.Action
Expand Down Expand Up @@ -387,6 +390,7 @@ extension Binding {
}

extension StoreProtocol {
@MainActor
public func binding<Value>(
get: @escaping (Self.Model) -> Value,
tag: @escaping (Value) -> Self.Model.Action
Expand Down
2 changes: 2 additions & 0 deletions Tests/ObservableStoreTests/BindingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ final class BindingTests: XCTestCase {
}

/// Test creating binding for an address
@MainActor
func testBinding() throws {
let store = Store(
state: Model(),
Expand Down Expand Up @@ -70,6 +71,7 @@ final class BindingTests: XCTestCase {
}

/// Test creating binding for an address
@MainActor
func testBindingMethod() throws {
let store = Store(
state: Model(),
Expand Down
4 changes: 4 additions & 0 deletions Tests/ObservableStoreTests/ComponentMappingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class ComponentMappingTests: XCTestCase {
}
}

@MainActor
func testForward() throws {
let store = Store(
state: ParentModel(),
Expand All @@ -150,6 +151,7 @@ class ComponentMappingTests: XCTestCase {
)
}

@MainActor
func testKeyedCursorUpdate() throws {
let store = Store(
state: ParentModel(
Expand Down Expand Up @@ -185,6 +187,7 @@ class ComponentMappingTests: XCTestCase {
)
}

@MainActor
func testCursorUpdate() throws {
let store = Store(
state: ParentModel(),
Expand All @@ -203,6 +206,7 @@ class ComponentMappingTests: XCTestCase {
)
}

@MainActor
func testKeyedCursorUpdateMissing() throws {
let store = Store(
state: ParentModel(
Expand Down
9 changes: 9 additions & 0 deletions Tests/ObservableStoreTests/ObservableStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ final class ObservableStoreTests: XCTestCase {
self.cancellables = Set()
}

@MainActor
func testStateAdvance() throws {
let store = Store(
state: AppModel(),
Expand All @@ -100,6 +101,7 @@ final class ObservableStoreTests: XCTestCase {
/// updates get removed from the cancellables array.
///
/// Failure to remove immediately-completing fx would cause a memory leak.
@MainActor
func testEmptyFxRemovedOnComplete() {
let store = Store(
state: AppModel(),
Expand Down Expand Up @@ -133,6 +135,7 @@ final class ObservableStoreTests: XCTestCase {
/// does not rely on an implementation detail of `Update` but instead
/// tests this behavior directly, in case the implementation were to
/// change somehow.
@MainActor
func testEmptyFxThatCompleteImmiedatelyRemovedOnComplete() {
let store = Store(
state: AppModel(),
Expand All @@ -155,6 +158,7 @@ final class ObservableStoreTests: XCTestCase {
wait(for: [expectation], timeout: 0.1)
}

@MainActor
func testAsyncFxRemovedOnComplete() {
let store = Store(
state: AppModel(),
Expand All @@ -176,6 +180,7 @@ final class ObservableStoreTests: XCTestCase {
wait(for: [expectation], timeout: 0.5)
}

@MainActor
func testPublishedPropertyFires() throws {
let store = Store(
state: AppModel(),
Expand Down Expand Up @@ -207,6 +212,7 @@ final class ObservableStoreTests: XCTestCase {
wait(for: [expectation], timeout: 0.2)
}

@MainActor
func testStateOnlySetWhenNotEqual() {
let store = Store(
state: AppModel(),
Expand Down Expand Up @@ -286,6 +292,7 @@ final class ObservableStoreTests: XCTestCase {
var subtitle: String = ""
}

@MainActor
func testUpdateMergeFx() {
let store = Store(
state: TestUpdateMergeFxState(),
Expand Down Expand Up @@ -318,6 +325,7 @@ final class ObservableStoreTests: XCTestCase {
wait(for: [expectation], timeout: 0.2)
}

@MainActor
func testCreateInit() throws {
let store = Store(
create: { environment in
Expand All @@ -343,6 +351,7 @@ final class ObservableStoreTests: XCTestCase {
wait(for: [expectation], timeout: 0.1)
}

@MainActor
func testActionsPublisher() throws {
let store = Store(
state: AppModel(),
Expand Down
1 change: 1 addition & 0 deletions Tests/ObservableStoreTests/UpdateActionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class UpdateActionsTests: XCTestCase {
}
}

@MainActor
func testUpdateActions() throws {
let store = Store(
state: TestModel(),
Expand Down
2 changes: 2 additions & 0 deletions Tests/ObservableStoreTests/ViewStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ final class ViewStoreTests: XCTestCase {
}

/// Test creating binding for an address
@MainActor
func testViewStore() throws {
let store = Store(
state: ParentModel(),
Expand All @@ -118,6 +119,7 @@ final class ViewStoreTests: XCTestCase {
}

/// Test creating binding for an address
@MainActor
func testViewStoreMethod() throws {
let store = Store(
state: ParentModel(),
Expand Down