Skip to content

Commit ff41e51

Browse files
authored
Limit async/await to Swift 5.5.2 (Xcode 13.2) (#923)
1 parent 83ae32a commit ff41e51

File tree

6 files changed

+245
-482
lines changed

6 files changed

+245
-482
lines changed
Lines changed: 94 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,211 +1,106 @@
11
import Combine
22
import SwiftUI
33

4-
#if compiler(>=5.5) && canImport(_Concurrency)
5-
#if compiler(>=5.5.2)
6-
extension Effect {
7-
/// Wraps an asynchronous unit of work in an effect.
8-
///
9-
/// This function is useful for executing work in an asynchronous context and capture the
10-
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
11-
///
12-
/// ```swift
13-
/// Effect.task {
14-
/// guard case let .some((data, _)) = try? await URLSession.shared
15-
/// .data(from: .init(string: "http://numbersapi.com/42")!)
16-
/// else {
17-
/// return "Could not load"
18-
/// }
19-
///
20-
/// return String(decoding: data, as: UTF8.self)
21-
/// }
22-
/// ```
23-
///
24-
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
25-
/// it is not recommended to use this function in reducers directly. Doing so will introduce
26-
/// thread hops into your effects that will make testing difficult. You will be responsible
27-
/// for adding explicit expectations to wait for small amounts of time so that effects can
28-
/// deliver their output.
29-
///
30-
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
31-
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
32-
///
33-
/// - Parameters:
34-
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
35-
/// `Task.currentPriority`.
36-
/// - operation: The operation to execute.
37-
/// - Returns: An effect wrapping the given asynchronous work.
38-
public static func task(
39-
priority: TaskPriority? = nil,
40-
operation: @escaping @Sendable () async -> Output
41-
) -> Self where Failure == Never {
42-
var task: Task<Void, Never>?
43-
return .future { callback in
44-
task = Task(priority: priority) {
45-
guard !Task.isCancelled else { return }
46-
let output = await operation()
47-
guard !Task.isCancelled else { return }
48-
callback(.success(output))
49-
}
50-
}
51-
.handleEvents(receiveCancel: { task?.cancel() })
52-
.eraseToEffect()
53-
}
54-
55-
/// Wraps an asynchronous unit of work in an effect.
56-
///
57-
/// This function is useful for executing work in an asynchronous context and capture the
58-
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
59-
///
60-
/// ```swift
61-
/// Effect.task {
62-
/// let (data, _) = try await URLSession.shared
63-
/// .data(from: .init(string: "http://numbersapi.com/42")!)
64-
///
65-
/// return String(decoding: data, as: UTF8.self)
66-
/// }
67-
/// ```
68-
///
69-
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
70-
/// it is not recommended to use this function in reducers directly. Doing so will introduce
71-
/// thread hops into your effects that will make testing difficult. You will be responsible
72-
/// for adding explicit expectations to wait for small amounts of time so that effects can
73-
/// deliver their output.
74-
///
75-
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
76-
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
77-
///
78-
/// - Parameters:
79-
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
80-
/// `Task.currentPriority`.
81-
/// - operation: The operation to execute.
82-
/// - Returns: An effect wrapping the given asynchronous work.
83-
public static func task(
84-
priority: TaskPriority? = nil,
85-
operation: @escaping @Sendable () async throws -> Output
86-
) -> Self where Failure == Error {
87-
Deferred<Publishers.HandleEvents<PassthroughSubject<Output, Failure>>> {
88-
let subject = PassthroughSubject<Output, Failure>()
89-
let task = Task(priority: priority) {
90-
do {
91-
try Task.checkCancellation()
92-
let output = try await operation()
93-
try Task.checkCancellation()
94-
subject.send(output)
95-
subject.send(completion: .finished)
96-
} catch is CancellationError {
97-
subject.send(completion: .finished)
98-
} catch {
99-
subject.send(completion: .failure(error))
100-
}
101-
}
102-
return subject.handleEvents(receiveCancel: task.cancel)
4+
#if canImport(_Concurrency) && compiler(>=5.5.2)
5+
extension Effect {
6+
/// Wraps an asynchronous unit of work in an effect.
7+
///
8+
/// This function is useful for executing work in an asynchronous context and capture the
9+
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
10+
///
11+
/// ```swift
12+
/// Effect.task {
13+
/// guard case let .some((data, _)) = try? await URLSession.shared
14+
/// .data(from: .init(string: "http://numbersapi.com/42")!)
15+
/// else {
16+
/// return "Could not load"
17+
/// }
18+
///
19+
/// return String(decoding: data, as: UTF8.self)
20+
/// }
21+
/// ```
22+
///
23+
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
24+
/// it is not recommended to use this function in reducers directly. Doing so will introduce
25+
/// thread hops into your effects that will make testing difficult. You will be responsible
26+
/// for adding explicit expectations to wait for small amounts of time so that effects can
27+
/// deliver their output.
28+
///
29+
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
30+
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
31+
///
32+
/// - Parameters:
33+
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
34+
/// `Task.currentPriority`.
35+
/// - operation: The operation to execute.
36+
/// - Returns: An effect wrapping the given asynchronous work.
37+
public static func task(
38+
priority: TaskPriority? = nil,
39+
operation: @escaping @Sendable () async -> Output
40+
) -> Self where Failure == Never {
41+
var task: Task<Void, Never>?
42+
return .future { callback in
43+
task = Task(priority: priority) {
44+
guard !Task.isCancelled else { return }
45+
let output = await operation()
46+
guard !Task.isCancelled else { return }
47+
callback(.success(output))
10348
}
104-
.eraseToEffect()
10549
}
50+
.handleEvents(receiveCancel: { task?.cancel() })
51+
.eraseToEffect()
10652
}
107-
#else
108-
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
109-
extension Effect {
110-
/// Wraps an asynchronous unit of work in an effect.
111-
///
112-
/// This function is useful for executing work in an asynchronous context and capture the
113-
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
114-
///
115-
/// ```swift
116-
/// Effect.task {
117-
/// guard case let .some((data, _)) = try? await URLSession.shared
118-
/// .data(from: .init(string: "http://numbersapi.com/42")!)
119-
/// else {
120-
/// return "Could not load"
121-
/// }
122-
///
123-
/// return String(decoding: data, as: UTF8.self)
124-
/// }
125-
/// ```
126-
///
127-
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
128-
/// it is not recommended to use this function in reducers directly. Doing so will introduce
129-
/// thread hops into your effects that will make testing difficult. You will be responsible
130-
/// for adding explicit expectations to wait for small amounts of time so that effects can
131-
/// deliver their output.
132-
///
133-
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
134-
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
135-
///
136-
/// - Parameters:
137-
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
138-
/// `Task.currentPriority`.
139-
/// - operation: The operation to execute.
140-
/// - Returns: An effect wrapping the given asynchronous work.
141-
public static func task(
142-
priority: TaskPriority? = nil,
143-
operation: @escaping @Sendable () async -> Output
144-
) -> Self where Failure == Never {
145-
var task: Task<Void, Never>?
146-
return .future { callback in
147-
task = Task(priority: priority) {
148-
guard !Task.isCancelled else { return }
149-
let output = await operation()
150-
guard !Task.isCancelled else { return }
151-
callback(.success(output))
152-
}
153-
}
154-
.handleEvents(receiveCancel: { task?.cancel() })
155-
.eraseToEffect()
156-
}
15753

158-
/// Wraps an asynchronous unit of work in an effect.
159-
///
160-
/// This function is useful for executing work in an asynchronous context and capture the
161-
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
162-
///
163-
/// ```swift
164-
/// Effect.task {
165-
/// let (data, _) = try await URLSession.shared
166-
/// .data(from: .init(string: "http://numbersapi.com/42")!)
167-
///
168-
/// return String(decoding: data, as: UTF8.self)
169-
/// }
170-
/// ```
171-
///
172-
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
173-
/// it is not recommended to use this function in reducers directly. Doing so will introduce
174-
/// thread hops into your effects that will make testing difficult. You will be responsible
175-
/// for adding explicit expectations to wait for small amounts of time so that effects can
176-
/// deliver their output.
177-
///
178-
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
179-
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
180-
///
181-
/// - Parameters:
182-
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
183-
/// `Task.currentPriority`.
184-
/// - operation: The operation to execute.
185-
/// - Returns: An effect wrapping the given asynchronous work.
186-
public static func task(
187-
priority: TaskPriority? = nil,
188-
operation: @escaping @Sendable () async throws -> Output
189-
) -> Self where Failure == Error {
190-
Deferred<Publishers.HandleEvents<PassthroughSubject<Output, Failure>>> {
191-
let subject = PassthroughSubject<Output, Failure>()
192-
let task = Task(priority: priority) {
193-
do {
194-
try Task.checkCancellation()
195-
let output = try await operation()
196-
try Task.checkCancellation()
197-
subject.send(output)
198-
subject.send(completion: .finished)
199-
} catch is CancellationError {
200-
subject.send(completion: .finished)
201-
} catch {
202-
subject.send(completion: .failure(error))
203-
}
54+
/// Wraps an asynchronous unit of work in an effect.
55+
///
56+
/// This function is useful for executing work in an asynchronous context and capture the
57+
/// result in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
58+
///
59+
/// ```swift
60+
/// Effect.task {
61+
/// let (data, _) = try await URLSession.shared
62+
/// .data(from: .init(string: "http://numbersapi.com/42")!)
63+
///
64+
/// return String(decoding: data, as: UTF8.self)
65+
/// }
66+
/// ```
67+
///
68+
/// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
69+
/// it is not recommended to use this function in reducers directly. Doing so will introduce
70+
/// thread hops into your effects that will make testing difficult. You will be responsible
71+
/// for adding explicit expectations to wait for small amounts of time so that effects can
72+
/// deliver their output.
73+
///
74+
/// Instead, this function is most helpful for calling `async`/`await` functions from the live
75+
/// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
76+
///
77+
/// - Parameters:
78+
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
79+
/// `Task.currentPriority`.
80+
/// - operation: The operation to execute.
81+
/// - Returns: An effect wrapping the given asynchronous work.
82+
public static func task(
83+
priority: TaskPriority? = nil,
84+
operation: @escaping @Sendable () async throws -> Output
85+
) -> Self where Failure == Error {
86+
Deferred<Publishers.HandleEvents<PassthroughSubject<Output, Failure>>> {
87+
let subject = PassthroughSubject<Output, Failure>()
88+
let task = Task(priority: priority) {
89+
do {
90+
try Task.checkCancellation()
91+
let output = try await operation()
92+
try Task.checkCancellation()
93+
subject.send(output)
94+
subject.send(completion: .finished)
95+
} catch is CancellationError {
96+
subject.send(completion: .finished)
97+
} catch {
98+
subject.send(completion: .failure(error))
20499
}
205-
return subject.handleEvents(receiveCancel: task.cancel)
206100
}
207-
.eraseToEffect()
101+
return subject.handleEvents(receiveCancel: task.cancel)
208102
}
103+
.eraseToEffect()
209104
}
210-
#endif
105+
}
211106
#endif

Sources/ComposableArchitecture/SwiftUI/Alert.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public struct AlertState<Action> {
185185
case cancel
186186
case destructive
187187

188-
#if compiler(>=5.5) && canImport(_Concurrency)
188+
#if compiler(>=5.5)
189189
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
190190
var toSwiftUI: SwiftUI.ButtonRole {
191191
switch self {
@@ -213,7 +213,7 @@ extension View {
213213
dismiss: Action
214214
) -> some View {
215215
WithViewStore(store, removeDuplicates: { $0?.id == $1?.id }) { viewStore in
216-
#if compiler(>=5.5) && canImport(_Concurrency)
216+
#if compiler(>=5.5)
217217
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
218218
self.alert(
219219
(viewStore.state?.title).map { Text($0) } ?? Text(""),
@@ -355,7 +355,7 @@ extension AlertState.Button {
355355
}
356356
}
357357

358-
#if compiler(>=5.5) && canImport(_Concurrency)
358+
#if compiler(>=5.5)
359359
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
360360
func toSwiftUIButton(send: @escaping (Action) -> Void) -> some View {
361361
SwiftUI.Button(
@@ -369,7 +369,7 @@ extension AlertState.Button {
369369
}
370370

371371
extension AlertState {
372-
#if compiler(>=5.5) && canImport(_Concurrency)
372+
#if compiler(>=5.5)
373373
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
374374
@ViewBuilder
375375
fileprivate func toSwiftUIActions(send: @escaping (Action) -> Void) -> some View {

Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public struct ConfirmationDialogState<Action> {
153153
case hidden
154154
case visible
155155

156-
#if compiler(>=5.5) && canImport(_Concurrency)
156+
#if compiler(>=5.5)
157157
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
158158
var toSwiftUI: SwiftUI.Visibility {
159159
switch self {
@@ -236,7 +236,7 @@ extension View {
236236
) -> some View {
237237

238238
WithViewStore(store, removeDuplicates: { $0?.id == $1?.id }) { viewStore in
239-
#if compiler(>=5.5) && canImport(_Concurrency)
239+
#if compiler(>=5.5)
240240
if #available(iOS 15, tvOS 15, watchOS 8, *) {
241241
self.confirmationDialog(
242242
(viewStore.state?.title).map { Text($0) } ?? Text(""),
@@ -267,7 +267,7 @@ extension View {
267267
@available(tvOS 13, *)
268268
@available(watchOS 6, *)
269269
extension ConfirmationDialogState {
270-
#if compiler(>=5.5) && canImport(_Concurrency)
270+
#if compiler(>=5.5)
271271
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
272272
@ViewBuilder
273273
fileprivate func toSwiftUIActions(send: @escaping (Action) -> Void) -> some View {

0 commit comments

Comments
 (0)