Skip to content

Commit fc17996

Browse files
committed
Merge branch 'navigation-beta' into prerelease/1.0
2 parents 132cb3b + 553214e commit fc17996

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+542
-298
lines changed

Package.resolved

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

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ let package = Package(
3939
.product(name: "IdentifiedCollections", package: "swift-identified-collections"),
4040
.product(name: "OrderedCollections", package: "swift-collections"),
4141
.product(name: "_SwiftUINavigationState", package: "swiftui-navigation"),
42+
// TODO: should we depend on this or copy some stuff over?
4243
.product(name: "SwiftUINavigation", package: "swiftui-navigation"),
4344
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
4445
]

Sources/ComposableArchitecture/Effects/Cancellation.swift

Lines changed: 72 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ extension EffectPublisher {
3030
/// - Returns: A new effect that is capable of being canceled by an identifier.
3131
public func cancellable(id: AnyHashable, cancelInFlight: Bool = false) -> Self {
3232
@Dependency(\.navigationIDPath) var navigationIDPath
33+
3334
switch self.operation {
3435
case .none:
3536
return .none
@@ -47,49 +48,37 @@ extension EffectPublisher {
4748
defer { _cancellablesLock.unlock() }
4849

4950
if cancelInFlight {
50-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
51-
_cancellationCancellables[cancelID]?.forEach { $0.cancel() }
51+
_cancellationCancellables.cancel(id: id, path: navigationIDPath)
5252
}
5353

5454
let cancellationSubject = PassthroughSubject<Void, Never>()
5555

56-
var cancellationCancellable: AnyCancellable!
57-
cancellationCancellable = AnyCancellable {
56+
var cancellable: AnyCancellable!
57+
cancellable = AnyCancellable {
5858
_cancellablesLock.sync {
5959
cancellationSubject.send(())
6060
cancellationSubject.send(completion: .finished)
61-
for navigationIDPath in navigationIDPath.prefixes {
62-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
63-
_cancellationCancellables[cancelID]?.remove(cancellationCancellable)
64-
if _cancellationCancellables[cancelID]?.isEmpty == .some(true) {
65-
_cancellationCancellables[cancelID] = nil
66-
}
67-
}
61+
_cancellationCancellables.remove(cancellable, at: id, path: navigationIDPath)
6862
}
6963
}
7064

7165
return publisher.prefix(untilOutputFrom: cancellationSubject)
7266
.handleEvents(
7367
receiveSubscription: { _ in
7468
_cancellablesLock.sync {
75-
for navigationIDPath in navigationIDPath.prefixes {
76-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
77-
_cancellationCancellables[cancelID, default: []].insert(
78-
cancellationCancellable
79-
)
80-
}
69+
_cancellationCancellables.insert(cancellable, at: id, path: navigationIDPath)
8170
}
8271
},
83-
receiveCompletion: { _ in cancellationCancellable.cancel() },
84-
receiveCancel: cancellationCancellable.cancel
72+
receiveCompletion: { _ in cancellable.cancel() },
73+
receiveCancel: cancellable.cancel
8574
)
8675
}
8776
.eraseToAnyPublisher()
8877
)
8978
)
9079
case let .run(priority, operation):
9180
return withEscapedDependencies { continuation in
92-
Self(
81+
return Self(
9382
operation: .run(priority) { send in
9483
await continuation.yield {
9584
await withTaskCancellation(id: id, cancelInFlight: cancelInFlight) {
@@ -124,11 +113,10 @@ extension EffectPublisher {
124113
public static func cancel(id: AnyHashable) -> Self {
125114
let dependencies = DependencyValues._current
126115
@Dependency(\.navigationIDPath) var navigationIDPath
127-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
128116
return Deferred { () -> Publishers.CompactMap<Result<Action?, Failure>.Publisher, Action> in
129117
DependencyValues.$_current.withValue(dependencies) {
130118
_cancellablesLock.sync {
131-
_cancellationCancellables[cancelID]?.forEach { $0.cancel() }
119+
_cancellationCancellables.cancel(id: id, path: navigationIDPath)
132120
}
133121
}
134122
return Just<Action?>(nil)
@@ -222,28 +210,19 @@ extension EffectPublisher {
222210
operation: @Sendable @escaping () async throws -> T
223211
) async rethrows -> T {
224212
@Dependency(\.navigationIDPath) var navigationIDPath
213+
225214
let (cancellable, task) = _cancellablesLock.sync { () -> (AnyCancellable, Task<T, Error>) in
226215
if cancelInFlight {
227-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
228-
_cancellationCancellables[cancelID]?.forEach { $0.cancel() }
216+
_cancellationCancellables.cancel(id: id, path: navigationIDPath)
229217
}
230218
let task = Task { try await operation() }
231219
let cancellable = AnyCancellable { task.cancel() }
232-
for navigationIDPath in navigationIDPath.prefixes {
233-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
234-
_cancellationCancellables[cancelID, default: []].insert(cancellable)
235-
}
220+
_cancellationCancellables.insert(cancellable, at: id, path: navigationIDPath)
236221
return (cancellable, task)
237222
}
238223
defer {
239224
_cancellablesLock.sync {
240-
for navigationIDPath in navigationIDPath.prefixes {
241-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
242-
_cancellationCancellables[cancelID]?.remove(cancellable)
243-
if _cancellationCancellables[cancelID]?.isEmpty == .some(true) {
244-
_cancellationCancellables[cancelID] = nil
245-
}
246-
}
225+
_cancellationCancellables.remove(cancellable, at: id, path: navigationIDPath)
247226
}
248227
}
249228
do {
@@ -259,28 +238,19 @@ extension EffectPublisher {
259238
operation: @Sendable @escaping () async throws -> T
260239
) async rethrows -> T {
261240
@Dependency(\.navigationIDPath) var navigationIDPath
241+
262242
let (cancellable, task) = _cancellablesLock.sync { () -> (AnyCancellable, Task<T, Error>) in
263243
if cancelInFlight {
264-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
265-
_cancellationCancellables[cancelID]?.forEach { $0.cancel() }
244+
_cancellationCancellables.cancel(id: id, path: navigationIDPath)
266245
}
267246
let task = Task { try await operation() }
268247
let cancellable = AnyCancellable { task.cancel() }
269-
for navigationIDPath in navigationIDPath.prefixes {
270-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
271-
_cancellationCancellables[cancelID, default: []].insert(cancellable)
272-
}
248+
_cancellationCancellables.insert(cancellable, at: id, path: navigationIDPath)
273249
return (cancellable, task)
274250
}
275251
defer {
276252
_cancellablesLock.sync {
277-
for navigationIDPath in navigationIDPath.prefixes {
278-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
279-
_cancellationCancellables[cancelID]?.remove(cancellable)
280-
if _cancellationCancellables[cancelID]?.isEmpty == .some(true) {
281-
_cancellationCancellables[cancelID] = nil
282-
}
283-
}
253+
_cancellationCancellables.remove(cancellable, at: id, path: navigationIDPath)
284254
}
285255
}
286256
do {
@@ -336,9 +306,9 @@ extension Task where Success == Never, Failure == Never {
336306
/// - Parameter id: An identifier.
337307
public static func cancel<ID: Hashable & Sendable>(id: ID) {
338308
@Dependency(\.navigationIDPath) var navigationIDPath
339-
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
309+
340310
return _cancellablesLock.sync {
341-
_cancellationCancellables[cancelID]?.forEach { $0.cancel() }
311+
_cancellationCancellables.cancel(id: id, path: navigationIDPath)
342312
}
343313
}
344314

@@ -354,22 +324,19 @@ extension Task where Success == Never, Failure == Never {
354324
}
355325

356326
@_spi(Internals) public struct _CancelID: Hashable {
327+
let discriminator: ObjectIdentifier
357328
let id: AnyHashable
358329
let navigationIDPath: NavigationIDPath
359330

360331
init(id: AnyHashable, navigationIDPath: NavigationIDPath) {
332+
self.discriminator = ObjectIdentifier(type(of: id.base))
361333
self.id = id
362334
self.navigationIDPath = navigationIDPath
363335
}
364-
365-
public init(_id id: AnyHashable) {
366-
self.id = id
367-
self.navigationIDPath = NavigationIDPath()
368-
}
369336
}
370337

371-
@_spi(Internals) public var _cancellationCancellables: [_CancelID: Set<AnyCancellable>] = [:]
372-
@_spi(Internals) public let _cancellablesLock = NSRecursiveLock()
338+
@_spi(Internals) public var _cancellationCancellables = CancellablesCollection()
339+
private let _cancellablesLock = NSRecursiveLock()
373340

374341
@rethrows
375342
private protocol _ErrorMechanism {
@@ -390,19 +357,56 @@ extension _ErrorMechanism {
390357

391358
extension Result: _ErrorMechanism {}
392359

360+
@_spi(Internals)
361+
public class CancellablesCollection {
362+
var storage: [_CancelID: Set<AnyCancellable>] = [:]
393363

364+
func insert(
365+
_ cancellable: AnyCancellable,
366+
at id: AnyHashable,
367+
path: NavigationIDPath
368+
) {
369+
for navigationIDPath in path.prefixes {
370+
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
371+
self.storage[cancelID, default: []].insert(cancellable)
372+
}
373+
}
394374

395-
/*
375+
func remove(
376+
_ cancellable: AnyCancellable,
377+
at id: AnyHashable,
378+
path: NavigationIDPath
379+
) {
380+
for navigationIDPath in path.prefixes {
381+
let cancelID = _CancelID(id: id, navigationIDPath: navigationIDPath)
382+
self.storage[cancelID]?.remove(cancellable)
383+
if self.storage[cancelID]?.isEmpty == true {
384+
self.storage[cancelID] = nil
385+
}
386+
}
387+
}
396388

397-
[1, 2, 3], TimerID
398-
[1]
389+
func cancel(
390+
id: AnyHashable,
391+
path: NavigationIDPath
392+
) {
393+
let cancelID = _CancelID(id: id, navigationIDPath: path)
394+
self.storage[cancelID]?.forEach { $0.cancel() }
395+
self.storage[cancelID] = nil
396+
}
397+
398+
func exists(
399+
at id: AnyHashable,
400+
path: NavigationIDPath
401+
) -> Bool {
402+
return self.storage[_CancelID(id: id, navigationIDPath: path)] != nil
403+
}
399404

400-
Trie<AnyID, [AnyHashable: Set<AnyCancellable>]>
401-
.insert(navigationID, [:])
402-
.modify(navigationID, default: [:]) {
403-
$0
404-
}
405+
public var count: Int {
406+
return self.storage.count
407+
}
405408

406-
trie[navigationID, default: [:]][id, default: []].insert(cancellable)
407-
trie[navigationID] = nil
408-
*/
409+
public func removeAll() {
410+
self.storage.removeAll()
411+
}
412+
}

Sources/ComposableArchitecture/Effects/Publisher.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@ extension EffectPublisher where Failure == Never {
88
public static func publisher<P: Publisher>(_ createPublisher: @escaping () -> P) -> Self
99
where P.Output == Action, P.Failure == Never {
1010
Self(
11-
operation: .publisher(Deferred(createPublisher: createPublisher).eraseToAnyPublisher())
11+
operation: .publisher(
12+
withEscapedDependencies { continuation in
13+
Deferred {
14+
continuation.yield {
15+
createPublisher()
16+
}
17+
}
18+
}
19+
.eraseToAnyPublisher()
20+
)
1221
)
1322
}
1423
}

Sources/ComposableArchitecture/Internal/EphemeralState.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extension AlertState: _EphemeralState {}
1111
@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *)
1212
extension ConfirmationDialogState: _EphemeralState {}
1313

14+
@usableFromInline
1415
func isEphemeral<State>(_ state: State) -> Bool {
1516
if State.self is _EphemeralState.Type {
1617
return true

Sources/ComposableArchitecture/Internal/NavigationID.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ private enum NavigationIDPathKey: DependencyKey {
1717
struct NavigationIDPath: Hashable, Identifiable, Sendable {
1818
fileprivate var path: [NavigationID]
1919

20+
@usableFromInline
2021
init(path: [NavigationID] = []) {
2122
self.path = path
2223
}

0 commit comments

Comments
 (0)