Skip to content

Commit 8d8290d

Browse files
committed
Support concurrency backdeployment (#916)
1 parent 3ff6150 commit 8d8290d

File tree

3 files changed

+255
-258
lines changed

3 files changed

+255
-258
lines changed

Sources/ComposableArchitecture/Beta/Concurrency.swift

Lines changed: 0 additions & 258 deletions
This file was deleted.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import ReactiveSwift
2+
#if canImport(_Concurrency) && compiler(>=5.5.2)
3+
extension Effect {
4+
/// Wraps an asynchronous unit of work in an effect.
5+
///
6+
/// This function is useful for executing work in an asynchronous context and capture the
7+
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
8+
///
9+
/// ```swift
10+
/// Effect.task {
11+
/// guard case let .some((data, _)) = try? await URLSession.shared
12+
/// .data(from: .init(string: "http://numbersapi.com/42")!)
13+
/// else {
14+
/// return "Could not load"
15+
/// }
16+
///
17+
/// return String(decoding: data, as: UTF8.self)
18+
/// }
19+
/// ```
20+
///
21+
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
22+
/// it is not recommended to use this function in reducers directly. Doing so will introduce
23+
/// thread hops into your effects that will make testing difficult. You will be responsible
24+
/// for adding explicit expectations to wait for small amounts of time so that effects can
25+
/// deliver their output.
26+
///
27+
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
28+
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
29+
///
30+
/// - Parameters:
31+
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
32+
/// `Task.currentPriority`.
33+
/// - operation: The operation to execute.
34+
/// - Returns: An effect wrapping the given asynchronous work.
35+
public static func task(
36+
priority: TaskPriority? = nil,
37+
operation: @escaping @Sendable () async -> Value
38+
) -> Self where Error == Never {
39+
var task: Task<Void, Never>?
40+
return .future { callback in
41+
task = Task(priority: priority) {
42+
guard !Task.isCancelled else { return }
43+
let output = await operation()
44+
guard !Task.isCancelled else { return }
45+
callback(.success(output))
46+
}
47+
}
48+
.on(disposed: { task?.cancel() })
49+
}
50+
51+
/// Wraps an asynchronous unit of work in an effect.
52+
///
53+
/// This function is useful for executing work in an asynchronous context and capture the
54+
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
55+
///
56+
/// ```swift
57+
/// Effect.task {
58+
/// let (data, _) = try await URLSession.shared
59+
/// .data(from: .init(string: "http://numbersapi.com/42")!)
60+
///
61+
/// return String(decoding: data, as: UTF8.self)
62+
/// }
63+
/// ```
64+
///
65+
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
66+
/// it is not recommended to use this function in reducers directly. Doing so will introduce
67+
/// thread hops into your effects that will make testing difficult. You will be responsible
68+
/// for adding explicit expectations to wait for small amounts of time so that effects can
69+
/// deliver their output.
70+
///
71+
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
72+
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
73+
///
74+
/// - Parameters:
75+
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
76+
/// `Task.currentPriority`.
77+
/// - operation: The operation to execute.
78+
/// - Returns: An effect wrapping the given asynchronous work.
79+
public static func task(
80+
priority: TaskPriority? = nil,
81+
operation: @escaping @Sendable () async throws -> Value
82+
) -> Self where Error == Swift.Error {
83+
deferred {
84+
var task: Task<(), Never>?
85+
let producer = SignalProducer { observer, lifetime in
86+
task = Task(priority: priority) {
87+
do {
88+
try Task.checkCancellation()
89+
let output = try await operation()
90+
try Task.checkCancellation()
91+
observer.send(value: output)
92+
observer.sendCompleted()
93+
} catch is CancellationError {
94+
observer.sendCompleted()
95+
} catch {
96+
observer.send(error: error)
97+
}
98+
}
99+
}
100+
101+
return producer.on(disposed: task?.cancel)
102+
}
103+
}
104+
}
105+
#endif

0 commit comments

Comments
 (0)