Skip to content

Commit

Permalink
Swift6 - Strict concurrency compatibility (#93)
Browse files Browse the repository at this point in the history
* unchecked sendable store

* enabled strict concurrency

* sendable effects

* fixed debounce to const

* StoreObjectProtocol sendable

* sendable conformance

* fixed almost all swift concurrency warnings

* fixed ui related warning

* changed DI to non-isolated

* minor fixes

* fixed tests

* fixed tests

* fixed tests for swift concurrency

* fixes

* removed sendable keypath workaround

* minor fix

* minor fixes

* removed strict concurrency flag
  • Loading branch information
KazaiMazai authored Sep 9, 2024
1 parent 333c70d commit f5893b1
Show file tree
Hide file tree
Showing 34 changed files with 261 additions and 201 deletions.
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ let package = Package(
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
.target(
.target(
name: "Puredux",
dependencies: [
"PureduxMacros",
"PureduxMacrosPlugin"
]),

]
),
.testTarget(
name: "PureduxTests",
dependencies: ["Puredux"]),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Puredux/SideEffects/AsyncAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation
import Dispatch

public protocol AsyncAction {
public protocol AsyncAction: Sendable {
associatedtype ResultAction
var dispatchQueue: DispatchQueue { get }
func execute(completeHandler: @escaping (ResultAction) -> Void)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Puredux/SideEffects/CancellableObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

class CancellableObserver {
final class CancellableObserver {
private class AnyStateObserver { }

var observer: AnyObject = AnyStateObserver()
Expand Down
8 changes: 4 additions & 4 deletions Sources/Puredux/SideEffects/Effect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

import Foundation

public struct Effect {
typealias Operation = () -> Void
public struct Effect: Sendable {
typealias Operation = @Sendable () -> Void
private let perform: Operation?
}

Expand All @@ -18,7 +18,7 @@ public extension Effect {

- Parameter operation: A closure representing the operation to be performed.
*/
init(_ operation: @escaping () -> Void) {
init(_ operation: @Sendable @escaping () -> Void) {
perform = operation
}

Expand All @@ -29,7 +29,7 @@ public extension Effect {

- Parameter operation: An asynchronous closure representing the operation to be performed.
*/
init(operation: @escaping () async -> Void) {
init(operation: @Sendable @escaping () async -> Void) {
perform = {
Task { await operation() }
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Puredux/SideEffects/EffectOperator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Dispatch
import Foundation

final class EffectOperator {
final class EffectOperator: @unchecked Sendable {
private(set) var executing: [Effect.State: DispatchWorkItem] = [:]
private(set) var isSynced = true

Expand Down
14 changes: 7 additions & 7 deletions Sources/Puredux/SideEffects/SideEffects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import Dispatch
import Foundation

public extension Store {
typealias CreateEffectForState = (State, Effect.State, @escaping Dispatch<Action>) -> Effect
typealias CreateEffect = (State, @escaping Dispatch<Action>) -> Effect
typealias CreateEffectForState = @Sendable (State, Effect.State, @escaping Dispatch<Action>) -> Effect
typealias CreateEffect = @Sendable (State, @escaping Dispatch<Action>) -> Effect
}

public extension Store {
Expand Down Expand Up @@ -63,7 +63,7 @@ public extension Store {
let weakStore = weakStore()

subscribe(observer: Observer(
storeObject(),
eraseToAnyStoreObject(),
removeStateDuplicates: .keyPath(keyPath)) { [effectOperator] state, prevState in
let effect = state[keyPath: keyPath]
effectOperator.run(effect, on: queue) { _ in
Expand Down Expand Up @@ -135,7 +135,7 @@ public extension Store {
let weakStore = weakStore()

subscribe(observer: Observer(
storeObject(),
eraseToAnyStoreObject(),
removeStateDuplicates: .keyPath(keyPath)) { [effectOperator] state, prevState in
let effect: Effect.State = prevState == nil ? .idle() : .running()
effectOperator.run(effect, on: queue) { _ in
Expand Down Expand Up @@ -200,7 +200,7 @@ public extension Store {
let weakStore = weakStore()

subscribe(observer: Observer(
storeObject(),
eraseToAnyStoreObject(),
removeStateDuplicates: .keyPath(keyPath)) { [effectOperator] state, prevState in
let isRunning = state[keyPath: keyPath]
effectOperator.run(isRunning, on: queue) { _ in
Expand Down Expand Up @@ -275,7 +275,7 @@ public extension Store {
let weakStore = weakStore()

subscribe(observer: Observer(
storeObject(),
eraseToAnyStoreObject(),
removeStateDuplicates: .keyPath(keyPath)) { [effectOperator] state, prevState in
let allEffects = state[keyPath: keyPath]
effectOperator.run(allEffects, on: queue) { effectState in
Expand All @@ -302,7 +302,7 @@ extension Store {
let weakStore = weakStore()

subscribe(observer: Observer(
storeObject(),
eraseToAnyStoreObject(),
removeStateDuplicates: removeStateDuplicates) { [effectOperator] state, prevState in
effectOperator.run(.running(delay: timeInterval), on: queue) { _ in
create(state, weakStore.dispatch)
Expand Down
13 changes: 7 additions & 6 deletions Sources/Puredux/Store/AnyStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@

import Foundation

public typealias Dispatch<Action> = (_ action: Action) -> Void
public typealias Dispatch<Action: Sendable> = @Sendable (_ action: Action) -> Void

typealias Subscribe<State> = (_ observer: Observer<State>) -> Void
typealias Subscribe<State> = @Sendable (_ observer: Observer<State>) -> Void

typealias ReferencedStore<State, Action> = ReferencedObject<AnyStoreObject<State, Action>>

public struct AnyStore<State, Action>: Store {
public struct AnyStore<State, Action>: Store where State: Sendable,
Action: Sendable {
let dispatchHandler: Dispatch<Action>
let subscriptionHandler: Subscribe<State>
let referencedStore: ReferencedStore<State, Action>
Expand All @@ -22,7 +23,7 @@ public struct AnyStore<State, Action>: Store {
public extension AnyStore {
func eraseToAnyStore() -> AnyStore<State, Action> { self }

func dispatch(_ action: Action) {
@Sendable func dispatch(_ action: Action) {
dispatchHandler(action)
executeAsyncAction(action)
}
Expand All @@ -45,7 +46,7 @@ public extension AnyStore {
- Parameter transform: A closure that takes the current state of type `State` and returns a new state of type `T`.
- Returns: A new `Store` with the transformed state of type `T` and the same action type `Action`.
*/
func map<T>(_ transform: @escaping (State) -> T) -> AnyStore<T, Action> {
func map<T>(_ transform: @Sendable @escaping (State) -> T) -> AnyStore<T, Action> {
AnyStore<T, Action>(
dispatcher: dispatchHandler,
subscribe: { localStateObserver in
Expand All @@ -69,7 +70,7 @@ public extension AnyStore {
- Returns: A new `Store` with the transformed state of type `T?` (optional) and the same action type `Action`.
- Note: This method differs from `compactMap(_:)` in that it preserves the `nil` values returned by the transformation closure, resulting in a store where the state type is optional.
*/
func flatMap<T>(_ transform: @escaping (State) -> T?) -> AnyStore<T?, Action> {
func flatMap<T>(_ transform: @Sendable @escaping (State) -> T?) -> AnyStore<T?, Action> {
AnyStore<T?, Action>(
dispatcher: dispatchHandler,
subscribe: { localStateObserver in
Expand Down
7 changes: 4 additions & 3 deletions Sources/Puredux/Store/Core/AnyStoreObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

import Foundation

final class AnyStoreObject<State, Action>: StoreObjectProtocol {
final class AnyStoreObject<State, Action>: StoreObjectProtocol, Sendable where State: Sendable,
Action: Sendable {

private let boxed: BaseBox

Expand Down Expand Up @@ -53,7 +54,7 @@ final class AnyStoreObject<State, Action>: StoreObjectProtocol {
}

extension AnyStoreObject {
func map<T>(_ transform: @escaping (State) -> T) -> AnyStoreObject<T, Action> {
func map<T>(_ transform: @Sendable @escaping (State) -> T) -> AnyStoreObject<T, Action> {
AnyStoreObject<T, Action>(boxed.map(transform: transform))
}
}
Expand Down Expand Up @@ -105,7 +106,7 @@ private extension AnyStoreObject {
}

private extension AnyStoreObject {
class BaseBox: StoreObjectProtocol {
class BaseBox: StoreObjectProtocol, @unchecked Sendable {
init() {
guard type(of: self) != BaseBox.self else {
fatalError("Cannot initialise, must subclass")
Expand Down
3 changes: 2 additions & 1 deletion Sources/Puredux/Store/Core/CoreStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public typealias Interceptor<Action> = (Action, @escaping Dispatch<Action>) -> V

typealias StoreID = UUID

final class CoreStore<State, Action> {
final class CoreStore<State, Action>: @unchecked Sendable where State: Sendable,
Action: Sendable {
let id: StoreID = StoreID()

private static var queueLabel: String { "com.puredux.store" }
Expand Down
13 changes: 9 additions & 4 deletions Sources/Puredux/Store/Core/StoreNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@ typealias VoidStore<Action> = CoreStore<Void, Action>

typealias RootStoreNode<State, Action> = StoreNode<VoidStore<Action>, State, State, Action>

final class StoreNode<ParentStore, LocalState, State, Action> where ParentStore: StoreObjectProtocol,
ParentStore.Action == Action {
final class StoreNode<ParentStore, LocalState, State, Action>: @unchecked Sendable
where
LocalState: Sendable,
State: Sendable,
Action: Sendable,
ParentStore: StoreObjectProtocol,
ParentStore.Action == Action {

private let localStore: CoreStore<LocalState, Action>
private let parentStore: ParentStore

private let stateMapping: (ParentStore.State, LocalState) -> State
private let stateMapping: @Sendable (ParentStore.State, LocalState) -> State

private var observers: Set<Observer<State>> = []

init(initialState: LocalState,
stateMapping: @escaping (ParentStore.State, LocalState) -> State,
stateMapping: @Sendable @escaping (ParentStore.State, LocalState) -> State,
parentStore: ParentStore,
reducer: @escaping Reducer<LocalState, Action>) {

Expand Down
21 changes: 15 additions & 6 deletions Sources/Puredux/Store/Core/StoreObjectProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

import Foundation

protocol StoreObjectProtocol<State, Action>: AnyObject & SyncStore {
protocol StoreObjectProtocol<State, Action>: AnyObject, SyncStore where Action: Sendable,
State: Sendable {
associatedtype Action
associatedtype State

Expand Down Expand Up @@ -36,8 +37,12 @@ protocol SyncStore<State, Action> {
extension StoreObjectProtocol {
func createChildStore<LocalState, ResultState>(
initialState: LocalState,
stateMapping: @escaping (Self.State, LocalState) -> ResultState,
reducer: @escaping Reducer<LocalState, Action>) -> any StoreObjectProtocol<ResultState, Action> {
stateMapping: @Sendable @escaping (Self.State, LocalState) -> ResultState,
reducer: @escaping Reducer<LocalState, Action>) -> any StoreObjectProtocol<ResultState, Action>

where
LocalState: Sendable,
ResultState: Sendable {

StoreNode<Self, LocalState, ResultState, Action>(
initialState: initialState,
Expand All @@ -49,8 +54,12 @@ extension StoreObjectProtocol {

func createChildStore<T, LocalState>(
initialState: LocalState,
transform: @escaping (State) -> T,
reducer: @escaping Reducer<LocalState, Action>) -> any StoreObjectProtocol<(T, LocalState), Action> {
transform: @Sendable @escaping (State) -> T,
reducer: @escaping Reducer<LocalState, Action>) -> any StoreObjectProtocol<(T, LocalState), Action>

where
LocalState: Sendable,
T: Sendable {

StoreNode(
initialState: initialState,
Expand All @@ -60,7 +69,7 @@ extension StoreObjectProtocol {
)
}

func map<T>(transform: @escaping (State) -> T) -> any StoreObjectProtocol<T, Action> {
func map<T: Sendable>(transform: @Sendable @escaping (State) -> T) -> any StoreObjectProtocol<T, Action> {
StoreNode(
initialState: Void(),
stateMapping: { state, _ in transform(state) },
Expand Down
2 changes: 1 addition & 1 deletion Sources/Puredux/Store/DI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
public struct Injected {

/** This is only used as an accessor to the computed properties within extensions of `InjectedValues`. */
private static var current = Injected()
nonisolated(unsafe) private static var current = Injected()

/** A static subscript for updating the `currentValue` of `InjectionKey` instances. */
static subscript<K>(key: K.Type) -> K.Value where K: InjectionKey {
Expand Down
15 changes: 7 additions & 8 deletions Sources/Puredux/Store/Observer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,24 @@ public enum ObserverStatus {
}

public extension Observer {

/**
Observer's main closure that handle State changes and calls complete handler
- Parameter state: newly observed State
- Parameter complete: complete handler that Observer calls when the work is done
*/
typealias StateHandler = (_ state: State) -> ObserverStatus
typealias StateHandler = @Sendable (_ state: State) -> ObserverStatus
}

/** Observer can be subscribed to Store to handle state updates */
public struct Observer<State>: Hashable {
typealias LastStatesHandler = (_ state: State, _ prev: State?) -> ObserverStatus
typealias ObserverHandler = (_ state: State, _ prev: State?) -> (ObserverStatus, State?)
public struct Observer<State>: Hashable, Sendable {
typealias LastStatesHandler = @Sendable (_ state: State, _ prev: State?) -> ObserverStatus
typealias ObserverHandler = @Sendable (_ state: State, _ prev: State?) -> (ObserverStatus, State?)

let id: UUID

private let statesObserver: ObserverHandler
private let keepPrevState: Bool
private let prevState: Referenced<State?> = Referenced(nil)
private let prevState: UncheckedReference<State?> = UncheckedReference(nil)

init(id: UUID = UUID(),
keepPrevState: Bool,
Expand Down Expand Up @@ -89,7 +88,7 @@ public extension Observer {
// MARK: - Observer attached to Object's lifecycle

extension Observer {
init(_ observer: AnyObject?,
init<ObserverObject: AnyObject & Sendable>(_ observer: ObserverObject?,
id: UUID = UUID(),
removeStateDuplicates equating: Equating<State>? = nil,
observe: @escaping StateHandler) {
Expand All @@ -99,7 +98,7 @@ extension Observer {
}
}

init(_ observer: AnyObject?,
init<ObserverObject: AnyObject & Sendable>(_ observer: ObserverObject?,
id: UUID = UUID(),
removeStateDuplicates equating: Equating<State>? = nil,
observe: @escaping ObserverHandler) {
Expand Down
10 changes: 6 additions & 4 deletions Sources/Puredux/Store/StateStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ public typealias Reducer<State, Action> = (inout State, Action) -> Void
- State: The type of the state managed by the store.
- Action: The type of actions that can be dispatched to the store.
*/
public struct StateStore<State, Action> {
public struct StateStore<State, Action>: Sendable where State: Sendable,
Action: Sendable {

let storeObject: AnyStoreObject<State, Action>

/**
Dispatches an action to the store, which will be processed by the reducer to update the state.

- Parameter action: The action to be dispatched.
*/
public func dispatch(_ action: Action) {
@Sendable public func dispatch(_ action: Action) {
storeObject.dispatch(action)
executeAsyncAction(action)
}
Expand Down Expand Up @@ -216,7 +218,7 @@ extension StateStore: AsyncActionsExecutor {
// MARK: - Basic Transformations

extension StateStore {
func map<T>(_ transformation: @escaping (State) -> T) -> StateStore<T, Action> {
func map<T>(_ transformation: @Sendable @escaping (State) -> T) -> StateStore<T, Action> {
StateStore<T, Action>(
storeObject: storeObject.createChildStore(
initialState: Void(),
Expand All @@ -226,7 +228,7 @@ extension StateStore {
)
}

func flatMap<T>(_ transformation: @escaping (State) -> T?) -> StateStore<T?, Action> {
func flatMap<T>(_ transformation: @Sendable @escaping (State) -> T?) -> StateStore<T?, Action> {
StateStore<T?, Action>(
storeObject: storeObject.createChildStore(
initialState: Void(),
Expand Down
Loading

0 comments on commit f5893b1

Please sign in to comment.