mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-20 09:11:33 +01:00
Fix iOS 14 Cancellation Crash (#244)
* wip * wip Co-authored-by: Brandon Williams <mbw234@gmail.com>
This commit is contained in:
@@ -27,6 +27,30 @@ extension Effect {
|
||||
/// canceled before starting this new one.
|
||||
/// - Returns: A new effect that is capable of being canceled by an identifier.
|
||||
public func cancellable(id: AnyHashable, cancelInFlight: Bool = false) -> Effect {
|
||||
// NB: This check intends to work around bugs over different versions of Combine
|
||||
#if swift(>=5.3) && !os(macOS)
|
||||
let effect = Deferred<
|
||||
Publishers.PrefixUntilOutput<Publishers.HandleEvents<Self>, PassthroughSubject<Void, Never>>
|
||||
> {
|
||||
let subject = PassthroughSubject<Void, Never>()
|
||||
lock.sync { subjects[id, default: []].append(subject) }
|
||||
let cleanup = {
|
||||
lock.sync {
|
||||
subjects[id]?.removeAll(where: { $0 === subject })
|
||||
if subjects[id]?.isEmpty == true {
|
||||
subjects[id] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return self
|
||||
.handleEvents(
|
||||
receiveCompletion: { _ in cleanup() },
|
||||
receiveCancel: cleanup
|
||||
)
|
||||
.prefix(untilOutputFrom: subject)
|
||||
}
|
||||
.eraseToEffect()
|
||||
#else
|
||||
let effect = Deferred { () -> Publishers.HandleEvents<PassthroughSubject<Output, Failure>> in
|
||||
cancellablesLock.lock()
|
||||
defer { cancellablesLock.unlock() }
|
||||
@@ -56,6 +80,7 @@ extension Effect {
|
||||
)
|
||||
}
|
||||
.eraseToEffect()
|
||||
#endif
|
||||
|
||||
return cancelInFlight ? .concatenate(.cancel(id: id), effect) : effect
|
||||
}
|
||||
@@ -66,13 +91,26 @@ extension Effect {
|
||||
/// - Returns: A new effect that will cancel any currently in-flight effect with the given
|
||||
/// identifier.
|
||||
public static func cancel(id: AnyHashable) -> Effect {
|
||||
#if swift(>=5.3) && !os(macOS)
|
||||
return .fireAndForget {
|
||||
lock.sync {
|
||||
subjects[id]?.forEach { $0.send(()) }
|
||||
}
|
||||
}
|
||||
#else
|
||||
return .fireAndForget {
|
||||
cancellablesLock.sync {
|
||||
cancellationCancellables[id]?.forEach { $0.cancel() }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.3) && !os(macOS)
|
||||
var subjects: [AnyHashable: [PassthroughSubject<Void, Never>]] = [:]
|
||||
let lock = NSRecursiveLock()
|
||||
#else
|
||||
var cancellationCancellables: [AnyHashable: Set<AnyCancellable>] = [:]
|
||||
let cancellablesLock = NSRecursiveLock()
|
||||
#endif
|
||||
|
||||
@@ -116,7 +116,11 @@ final class EffectCancellationTests: XCTestCase {
|
||||
.sink(receiveValue: { _ in })
|
||||
.store(in: &self.cancellables)
|
||||
|
||||
XCTAssertEqual([:], cancellationCancellables)
|
||||
#if swift(>=5.3) && !os(macOS)
|
||||
XCTAssertTrue(subjects.isEmpty)
|
||||
#else
|
||||
XCTAssertTrue(cancellationCancellables.isEmpty)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testCancellablesCleanUp_OnCancel() {
|
||||
@@ -132,7 +136,11 @@ final class EffectCancellationTests: XCTestCase {
|
||||
.sink(receiveValue: { _ in })
|
||||
.store(in: &self.cancellables)
|
||||
|
||||
XCTAssertEqual([:], cancellationCancellables)
|
||||
#if swift(>=5.3) && !os(macOS)
|
||||
XCTAssertTrue(subjects.isEmpty)
|
||||
#else
|
||||
XCTAssertTrue(cancellationCancellables.isEmpty)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testDoubleCancellation() {
|
||||
@@ -222,7 +230,11 @@ final class EffectCancellationTests: XCTestCase {
|
||||
.store(in: &self.cancellables)
|
||||
self.wait(for: [expectation], timeout: 999)
|
||||
|
||||
#if swift(>=5.3) && !os(macOS)
|
||||
XCTAssertTrue(subjects.isEmpty)
|
||||
#else
|
||||
XCTAssertTrue(cancellationCancellables.isEmpty)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testNestedCancels() {
|
||||
@@ -240,7 +252,11 @@ final class EffectCancellationTests: XCTestCase {
|
||||
|
||||
cancellables.removeAll()
|
||||
|
||||
XCTAssertEqual([:], cancellationCancellables)
|
||||
#if swift(>=5.3) && !os(macOS)
|
||||
XCTAssertTrue(subjects.isEmpty)
|
||||
#else
|
||||
XCTAssertTrue(cancellationCancellables.isEmpty)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testSharedId() {
|
||||
|
||||
Reference in New Issue
Block a user