Skip to content

Commit 0d8980f

Browse files
Swift 6: Main actor isolated store view helpers (#3283)
* `@preconcurrency @MainActor` isolation of `Store` * Remove unneeded `@MainActor`s * Remove thread checking code * Remove unneeded `@MainActor`s * Swift 5.10 compatibility fixes * wip * More 5.10 fixes * wip * fixes * wip * wip * up the timeout * wip * Fixes * wip * wip * wip * wip * wip * wip * Main actor logger * wip * wip * wip * fix * fix * fix * fix compilation error * wip * wip * bring back yield --------- Co-authored-by: Brandon Williams <[email protected]>
1 parent dfa29e0 commit 0d8980f

28 files changed

+375
-88
lines changed

ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,11 @@ let package = Package(
8686
]
8787
)
8888

89-
//for target in package.targets where target.type != .system {
90-
// target.swiftSettings = target.swiftSettings ?? []
91-
// target.swiftSettings?.append(
92-
// .unsafeFlags([
93-
// "-c", "release",
94-
// "-emit-module-interface", "-enable-library-evolution",
95-
// "-Xfrontend", "-warn-concurrency",
96-
// "-Xfrontend", "-enable-actor-data-race-checks",
97-
// ])
98-
// )
99-
//}
89+
#if compiler(>=6)
90+
for target in package.targets where target.type != .system {
91+
target.swiftSettings = target.swiftSettings ?? []
92+
target.swiftSettings?.append(contentsOf: [
93+
.enableExperimentalFeature("StrictConcurrency"),
94+
])
95+
}
96+
#endif

Sources/ComposableArchitecture/Dependencies/Dismiss.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,5 @@ extension DismissEffect {
152152

153153
private enum DismissKey: DependencyKey {
154154
static let liveValue = DismissEffect()
155-
static var testValue = DismissEffect()
155+
static let testValue = DismissEffect()
156156
}

Sources/ComposableArchitecture/Internal/Logger.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import OSLog
22

33
@_spi(Logging)
4+
#if swift(<5.10)
5+
@MainActor(unsafe)
6+
#else
7+
@preconcurrency @MainActor
8+
#endif
49
public final class Logger {
510
public static let shared = Logger()
611
public var isEnabled = false
@@ -35,5 +40,5 @@ public final class Logger {
3540
#endif
3641
}
3742

38-
private var isRunningForPreviews =
43+
private let isRunningForPreviews =
3944
ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"

Sources/ComposableArchitecture/Observation/Alert+Observation.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import SwiftUI
33
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
44
extension View {
55
/// Presents an alert when a piece of optional state held in a store becomes non-`nil`.
6+
#if swift(<5.10)
7+
@MainActor(unsafe)
8+
#else
9+
@preconcurrency @MainActor
10+
#endif
611
public func alert<Action>(_ item: Binding<Store<AlertState<Action>, Action>?>) -> some View {
712
let store = item.wrappedValue
813
let alertState = store?.withState { $0 }
@@ -33,11 +38,13 @@ extension View {
3338
}
3439
)
3540
}
36-
}
3741

38-
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
39-
extension View {
4042
/// Presents an alert when a piece of optional state held in a store becomes non-`nil`.
43+
#if swift(<5.10)
44+
@MainActor(unsafe)
45+
#else
46+
@preconcurrency @MainActor
47+
#endif
4148
public func confirmationDialog<Action>(
4249
_ item: Binding<Store<ConfirmationDialogState<Action>, Action>?>
4350
) -> some View {

Sources/ComposableArchitecture/Observation/Binding+Observation.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@
172172
///
173173
/// - Parameter action: An action for the binding to send values through.
174174
/// - Returns: A binding.
175+
#if swift(<5.10)
176+
@MainActor(unsafe)
177+
#else
178+
@preconcurrency @MainActor
179+
#endif
175180
public func sending(_ action: CaseKeyPath<Action, Value>) -> Binding<Value> {
176181
self.binding[state: self.keyPath, action: action]
177182
}
@@ -220,6 +225,11 @@
220225
///
221226
/// - Parameter action: An action for the binding to send values through.
222227
/// - Returns: A binding.
228+
#if swift(<5.10)
229+
@MainActor(unsafe)
230+
#else
231+
@preconcurrency @MainActor
232+
#endif
223233
public func sending(_ action: CaseKeyPath<Action, Value>) -> Binding<Value> {
224234
self.bindable[state: self.keyPath, action: action]
225235
}
@@ -248,6 +258,11 @@
248258
///
249259
/// - Parameter action: An action for the binding to send values through.
250260
/// - Returns: A binding.
261+
#if swift(<5.10)
262+
@MainActor(unsafe)
263+
#else
264+
@preconcurrency @MainActor
265+
#endif
251266
public func sending(_ action: CaseKeyPath<Action, Value>) -> Binding<Value> {
252267
self.bindable[state: self.keyPath, action: action]
253268
}

Sources/ComposableArchitecture/Observation/NavigationStack+Observation.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ import SwiftUI
5757
/// }
5858
/// }
5959
/// ```
60+
#if swift(>=5.10)
61+
@preconcurrency @MainActor
62+
#else
63+
@MainActor(unsafe)
64+
#endif
6065
public func scope<State: ObservableState, Action, ElementState, ElementAction>(
6166
state: KeyPath<State, StackState<ElementState>>,
6267
action: CaseKeyPath<Action, StackAction<ElementState, ElementAction>>
@@ -72,6 +77,11 @@ import SwiftUI
7277
///
7378
/// See ``SwiftUI/Binding/scope(state:action:fileID:filePath:line:column:)`` defined on `Binding` for more
7479
/// information.
80+
#if swift(>=5.10)
81+
@preconcurrency @MainActor
82+
#else
83+
@MainActor(unsafe)
84+
#endif
7585
public func scope<State: ObservableState, Action, ElementState, ElementAction>(
7686
state: KeyPath<State, StackState<ElementState>>,
7787
action: CaseKeyPath<Action, StackAction<ElementState, ElementAction>>
@@ -104,6 +114,11 @@ import SwiftUI
104114
///
105115
/// See ``SwiftUI/Binding/scope(state:action:fileID:filePath:line:column:)`` defined on `Binding` for more
106116
/// information.
117+
#if swift(>=5.10)
118+
@preconcurrency @MainActor
119+
#else
120+
@MainActor(unsafe)
121+
#endif
107122
public func scope<State: ObservableState, Action, ElementState, ElementAction>(
108123
state: KeyPath<State, StackState<ElementState>>,
109124
action: CaseKeyPath<Action, StackAction<ElementState, ElementAction>>

Sources/ComposableArchitecture/Observation/Store+Observation.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
}
2727

2828
extension Store: Equatable {
29-
public static func == (lhs: Store, rhs: Store) -> Bool {
29+
public static nonisolated func == (lhs: Store, rhs: Store) -> Bool {
3030
lhs === rhs
3131
}
3232
}
3333

3434
extension Store: Hashable {
35-
public func hash(into hasher: inout Hasher) {
35+
public nonisolated func hash(into hasher: inout Hasher) {
3636
hasher.combine(ObjectIdentifier(self))
3737
}
3838
}
@@ -155,6 +155,11 @@
155155
/// - state: A key path to optional child state.
156156
/// - action: A case key path to presentation child actions.
157157
/// - Returns: A binding of an optional child store.
158+
#if swift(>=5.10)
159+
@preconcurrency @MainActor
160+
#else
161+
@MainActor(unsafe)
162+
#endif
158163
public func scope<State: ObservableState, Action, ChildState, ChildAction>(
159164
state: KeyPath<State, ChildState?>,
160165
action: CaseKeyPath<Action, PresentationAction<ChildAction>>,
@@ -225,6 +230,11 @@
225230
/// - state: A key path to optional child state.
226231
/// - action: A case key path to presentation child actions.
227232
/// - Returns: A binding of an optional child store.
233+
#if swift(>=5.10)
234+
@preconcurrency @MainActor
235+
#else
236+
@MainActor(unsafe)
237+
#endif
228238
public func scope<State: ObservableState, Action, ChildState, ChildAction>(
229239
state: KeyPath<State, ChildState?>,
230240
action: CaseKeyPath<Action, PresentationAction<ChildAction>>,
@@ -321,6 +331,11 @@
321331
}
322332

323333
extension UIBindable {
334+
#if swift(>=5.10)
335+
@preconcurrency @MainActor
336+
#else
337+
@MainActor(unsafe)
338+
#endif
324339
public func scope<State: ObservableState, Action, ChildState, ChildAction>(
325340
state: KeyPath<State, ChildState?>,
326341
action: CaseKeyPath<Action, PresentationAction<ChildAction>>,

Sources/ComposableArchitecture/Store.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ public final class Store<State, Action> {
193193
}
194194

195195
deinit {
196-
Logger.shared.log("\(storeTypeName(of: self)).deinit")
196+
guard Thread.isMainThread else { return }
197+
MainActor._assumeIsolated {
198+
Logger.shared.log("\(storeTypeName(of: self)).deinit")
199+
}
197200
}
198201

199202
/// Calls the given closure with a snapshot of the current state of the store.

Sources/ComposableArchitecture/SwiftUI/Alert.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ extension View {
88
/// - Parameters:
99
/// - store: A store that is focused on ``PresentationState`` and ``PresentationAction`` for an
1010
/// alert.
11+
#if swift(<5.10)
12+
@MainActor(unsafe)
13+
#else
14+
@preconcurrency @MainActor
15+
#endif
1116
public func alert<ButtonAction>(
1217
store: Store<PresentationState<AlertState<ButtonAction>>, PresentationAction<ButtonAction>>
1318
) -> some View {
@@ -43,6 +48,11 @@ extension View {
4348
message:
4449
"Further scope the store into the 'state' and 'action' cases, instead. For more information, see the following article: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.5#Enum-driven-navigation-APIs"
4550
)
51+
#if swift(<5.10)
52+
@MainActor(unsafe)
53+
#else
54+
@preconcurrency @MainActor
55+
#endif
4656
public func alert<State, Action, ButtonAction>(
4757
store: Store<PresentationState<State>, PresentationAction<Action>>,
4858
state toDestinationState: @escaping (_ state: State) -> AlertState<ButtonAction>?,
@@ -51,6 +61,11 @@ extension View {
5161
self._alert(store: store, state: toDestinationState, action: fromDestinationAction)
5262
}
5363

64+
#if swift(<5.10)
65+
@MainActor(unsafe)
66+
#else
67+
@preconcurrency @MainActor
68+
#endif
5469
private func _alert<State, Action, ButtonAction>(
5570
store: Store<PresentationState<State>, PresentationAction<Action>>,
5671
state toDestinationState: @escaping (_ state: State) -> AlertState<ButtonAction>?,

0 commit comments

Comments
 (0)