3
3
// Created by Freek (github.com/frzi) 2021
4
4
//
5
5
6
+ import Dispatch
6
7
import SwiftUI
7
8
8
9
/// EnvironmentObject storing the state of a Router.
@@ -15,7 +16,8 @@ import SwiftUI
15
16
/// ```swift
16
17
/// @EnvironmentObject var navigator: Navigator
17
18
/// ```
18
- public final class Navigator : ObservableObject {
19
+ public final class Navigator : ObservableObject , @unchecked Sendable {
20
+ private let serialAccessQueue = DispatchQueue ( label: " serialAccessQueue " , qos: . default)
19
21
20
22
@Published private var historyStack : [ String ]
21
23
@Published private var forwardStack : [ String ] = [ ]
@@ -84,29 +86,31 @@ public final class Navigator: ObservableObject {
84
86
/// - Parameter path: Path of the new location to navigate to.
85
87
/// - Parameter replace: if `true` will replace the last path in the history stack with the new path.
86
88
public func navigate( _ path: String , replace: Bool = false ) {
87
- let path = resolvePaths ( self . path, path)
88
- let previousPath = self . path
89
-
90
- guard path != previousPath else {
91
- #if DEBUG
92
- print ( " SwiftUIRouter: Navigating to the same path ignored. " )
93
- #endif
94
- return
95
- }
96
-
97
- forwardStack. removeAll ( )
98
-
99
- if replace && !historyStack. isEmpty {
100
- historyStack [ historyStack. endIndex - 1 ] = path
101
- }
102
- else {
103
- historyStack. append ( path)
104
- }
105
-
106
- lastAction = NavigationAction (
107
- currentPath: path,
108
- previousPath: previousPath,
109
- action: . push)
89
+ serialAccessQueue. sync {
90
+ let path = resolvePaths ( self . path, path)
91
+ let previousPath = self . path
92
+
93
+ guard path != previousPath else {
94
+ #if DEBUG
95
+ print ( " SwiftUIRouter: Navigating to the same path ignored. " )
96
+ #endif
97
+ return
98
+ }
99
+
100
+ forwardStack. removeAll ( )
101
+
102
+ if replace && !historyStack. isEmpty {
103
+ historyStack [ historyStack. endIndex - 1 ] = path
104
+ }
105
+ else {
106
+ historyStack. append ( path)
107
+ }
108
+
109
+ lastAction = NavigationAction (
110
+ currentPath: path,
111
+ previousPath: previousPath,
112
+ action: . push)
113
+ }
110
114
}
111
115
112
116
/// Go back *n* steps in the navigation history.
@@ -118,49 +122,54 @@ public final class Navigator: ObservableObject {
118
122
guard canGoBack else {
119
123
return
120
124
}
121
-
122
- let previousPath = path
123
-
124
- let total = min ( total, historyStack. count)
125
- let start = historyStack. count - total
126
- forwardStack. append ( contentsOf: historyStack [ start... ] . reversed ( ) )
127
- historyStack. removeLast ( total)
128
-
129
- lastAction = NavigationAction (
130
- currentPath: path,
131
- previousPath: previousPath,
132
- action: . back)
125
+ serialAccessQueue. sync {
126
+ let previousPath = path
127
+
128
+ let total = min ( total, historyStack. count)
129
+ let start = historyStack. count - total
130
+ forwardStack. append ( contentsOf: historyStack [ start... ] . reversed ( ) )
131
+ historyStack. removeLast ( total)
132
+
133
+ lastAction = NavigationAction (
134
+ currentPath: path,
135
+ previousPath: previousPath,
136
+ action: . back)
137
+ }
133
138
}
134
139
135
140
/// Go forward *n* steps in the navigation history.
136
141
///
137
142
/// `total` will always be clamped and thus prevent from going out of bounds.
138
143
///
139
144
/// - Parameter total: Total steps to go forward.
140
- public func goForward( total: Int = 1 ) {
141
- guard canGoForward else {
142
- return
143
- }
144
-
145
- let previousPath = path
146
-
147
- let total = min ( total, forwardStack. count)
148
- let start = forwardStack. count - total
149
- historyStack. append ( contentsOf: forwardStack [ start... ] )
150
- forwardStack. removeLast ( total)
151
-
152
- lastAction = NavigationAction (
153
- currentPath: path,
154
- previousPath: previousPath,
155
- action: . forward)
156
- }
145
+ public func goForward( total: Int = 1 ) {
146
+ guard canGoForward else {
147
+ return
148
+ }
149
+
150
+ serialAccessQueue. sync {
151
+ let previousPath = path
152
+
153
+ let total = min ( total, forwardStack. count)
154
+ let start = forwardStack. count - total
155
+ historyStack. append ( contentsOf: forwardStack [ start... ] )
156
+ forwardStack. removeLast ( total)
157
+
158
+ lastAction = NavigationAction (
159
+ currentPath: path,
160
+ previousPath: previousPath,
161
+ action: . forward)
162
+ }
163
+ }
157
164
158
165
/// Clear the entire navigation history.
159
- public func clear( ) {
160
- forwardStack. removeAll ( )
161
- historyStack = [ path]
162
- lastAction = nil
163
- }
166
+ public func clear( ) {
167
+ serialAccessQueue. sync {
168
+ forwardStack. removeAll ( )
169
+ historyStack = [ path]
170
+ lastAction = nil
171
+ }
172
+ }
164
173
}
165
174
166
175
extension Navigator : Equatable {
@@ -180,9 +189,9 @@ extension Navigator {
180
189
181
190
// MARK: -
182
191
/// Information about a navigation that occurred.
183
- public struct NavigationAction : Equatable {
192
+ public struct NavigationAction : Equatable , Sendable {
184
193
/// Directional difference between the current path and the previous path.
185
- public enum Direction {
194
+ public enum Direction : Sendable {
186
195
/// The new path is higher up in the hierarchy *or* a completely different path.
187
196
/// Example: `/user/settings` → `/user`. Or `/favorite/music` → `/news/latest`.
188
197
case higher
@@ -193,7 +202,7 @@ public struct NavigationAction: Equatable {
193
202
}
194
203
195
204
/// The kind of navigation that occurred.
196
- public enum Action {
205
+ public enum Action : Sendable {
197
206
/// Navigated to a new path.
198
207
case push
199
208
/// Navigated back in the stack.
0 commit comments