8000 Moving Store off main actor by Pearapps · Pull Request #17 · Lickability/ViewStore · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Moving Store off main actor #17

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

Merged
merged 2 commits into from
May 28, 2025
Merged
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
8000
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
//

import Foundation
import Combine
@preconcurrency import Combine

/// A really contrived fake interface similar to networking state controller for updating `Banner` models on a nonexistent server.
final class MockBannerNetworkStateController {
final class MockBannerNetworkStateController: Sendable {

/// Represents the state of a network request for a banner.
enum NetworkState {
Expand Down Expand Up @@ -67,22 +67,19 @@ final class MockBannerNetworkStateController {
}

/// A publisher that sends updates of the `NetworkState`.
public var publisher: PassthroughSubject<NetworkState, Never> = .init()
public let publisher: PassthroughSubject<NetworkState, Never> = .init()

/// Uploads a `Banner` to a fake server.
/// - Parameter banner: The `Banner` to upload.
@MainActor
func upload(banner: Banner) {
self.publisher.send(.inProgress)

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {

// Pick whether you would like to get a successful (`.finished(.success...`) state or any error for this "network request".

//self.publisher.send(.finished(.success(banner)))
self.publisher.send(.finished(.success(banner)))

self.publisher.send(.finished(.failure(.intentionalFailure)))

}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class BannerUpdateViewStore: Store {
// MARK: - Store

/// Represents the state of the `BannerUpdateViewStore`
struct State {
struct State: Sendable {
/// Stores the state of the nested `BannerDataStore`
let bannerViewState: BannerDataStore.State

Expand Down Expand Up @@ -51,7 +51,7 @@ final class BannerUpdateViewStore: Store {
}
}

enum Action {
enum Action: Sendable {
/// Action to update the title of the banner with a given string
case updateTitle(String)

Expand Down Expand Up @@ -106,11 +106,13 @@ final class BannerUpdateViewStore: Store {

extension BannerUpdateViewStoreType {
/// Computed property that creates a binding for the working title
@MainActor
var workingTitle: Binding<String> {
makeBinding(stateKeyPath: \.workingCopy.title, actionCasePath: /Action.updateTitle)
}

/// Computed property that creates a binding for the error presentation state
@MainActor
var isErrorPresented: Binding<Bool> {
.init(get: {
return self.state.error != nil
Expand Down
3 changes: 3 additions & 0 deletions Example/Photos/With Stores/PhotoListViewStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,13 @@ extension PhotoListViewStoreType {
}

/// Computed property that creates a binding for the `showUpdateView` state
@MainActor
var showUpdateView: Binding<Bool> {
makeBinding(stateKeyPath: \.showUpdateView, actionCasePath: /PhotoListViewStore.Action.showUpdateView)
}

/// Computed property that creates a binding for the `showsPhotoCount` state
@MainActor
var showsPhotoCount: Binding<Bool> {
//
// return Binding<Bool> {
Expand All @@ -183,6 +185,7 @@ extension PhotoListViewStoreType {
}

/// Computed property that creates a binding for the `searchText` state
@MainActor
var searchText: Binding<String> {
makeBinding(stateKeyPath: \.searchText, actionCasePath: /Action.search)
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/Store/MainQueueScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import SwiftUI

/// Scheduler that allows you to either have a `default` implementation of the `DispatchQueue.main` scheduler, a `synchronous` implementation that will immediately call back, a `test` scheduler for fine grained control of time, and an `animated` scheduler to drive animations.
/// You will want to use this to avoid the behavior of `DispatchQueue.main`'s scheduler to schedule work asynchronously by default.
@MainActor
public final class MainQueueScheduler: @preconcurrency Scheduler {
public final class MainQueueScheduler: Scheduler {

/// Describes the characteristics of the scheduler.
public enum SchedulerType: Equatable {
Expand Down
10 changes: 8 additions & 2 deletions Sources/Store/Store+BindingAdditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//

import SwiftUI
import CasePaths
import Combine
@preconcurrency import CasePaths
@preconcurrency import Combine

/// An extension on `Store` that provides conveniences for creating `Binding`s.
public extension Store {
Expand All @@ -16,6 +16,7 @@ public extension Store {
/// - Parameters:
/// - stateKeyPath: The `KeyPath` to the `State` property that this binding wraps.
/// - actionCasePath: The `CasePath` to the `Action` case associated with the `State` property.
@MainActor
func makeBinding<Value>(stateKeyPath: KeyPath<State, Value>, actionCasePath: CasePath<Action, Value>) -> Binding<Value> {
return .init {
self.state[keyPath: stateKeyPath]
Expand All @@ -28,6 +29,7 @@ public extension Store {
/// - Parameters:
/// - stateKeyPath: The `KeyPath` to the `State` property that this binding wraps.
/// - actionCasePath: The `CasePath` to the `Action` case associated with the `State` property.
@MainActor
func makeBinding<Value>(stateKeyPath: KeyPath<State, Value>, actionCasePath: CasePath<Action, Void>) -> Binding<Value> {
return .init {
self.state[keyPath: stateKeyPath]
Expand All @@ -40,6 +42,7 @@ public extension Store {
/// - Parameters:
/// - stateKeyPath: The `KeyPath` to the optional `State` property whose existence determines the wrapped value.
/// - actionCasePath: The `CasePath` to the `Action` case associated with the `State` property.
@MainActor
func makeBinding<Value>(stateKeyPath: KeyPath<State, Value?>, actionCasePath: CasePath<Action, Void>) -> Binding<Bool> {
return .init {
self.state[keyPath: stateKeyPath] != nil
Expand All @@ -52,6 +55,7 @@ public extension Store {
/// - Parameters:
/// - stateKeyPath: The `KeyPath` to the optional `State` property whose existence determines the wrapped value.
/// - actionCasePath: The `CasePath` to the `Action` case associated with the `State` property.
@MainActor
func makeBinding<Value>(stateKeyPath: KeyPath<State, Value?>, actionCasePath: CasePath<Action, Value?>) -> Binding<Bool> {
return .init {
self.state[keyPath: stateKeyPath] != nil
Expand All @@ -68,6 +72,7 @@ public extension Store {
/// - Parameters:
/// - stateKeyPath: The `KeyPath` to the `State` property that this binding wraps.
/// - publisher: The publisher to send the value to.
@MainActor
func makeBinding<Value>(stateKeyPath: KeyPath<State, Value>, publisher: PassthroughSubject<Value, Never>) -> Binding<Value> {
return .init {
self.state[keyPath: stateKeyPath]
Expand All @@ -80,6 +85,7 @@ public extension Store {
/// - Parameters:
/// - StateKeyPath: The `KeyPath` to the `State` property that this binding wraps.
/// - publisher: The publisher to send the value to.
@MainActor
func makeBinding<Value>(stateKeyPath: KeyPath<State, Value>, publisher: CurrentValueSubject<Value, Never>) -> Binding<Value> {
return .init {
self.state[keyPath: stateKeyPath]
Expand Down
1 change: 0 additions & 1 deletion Sources/Store/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import SwiftUI
import Combine

/// A store is an `ObservableObject` that allows us to separate business and/or view level logic and the rendering of views in a way that is repeatable, prescriptive, flexible, and testable by default.
@MainActor
public protocol Store<State, Action>: ObservableObject {

/// A container type for state associated with the corresponding domain.
Expand Down
0