Skip to content

Commit 5d4f96d

Browse files
authored
Soft-deprecate Effect.task and Effect.fireAndForget (#2099)
* Soft-deprecate `Effect.task` and `Effect.fireAndForget` See #1520 * wip * wip * wip
1 parent 8c691c1 commit 5d4f96d

File tree

46 files changed

+237
-291
lines changed

Some content is hidden

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

46 files changed

+237
-291
lines changed

ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved

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

Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Basics.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ struct EffectsBasics: ReducerProtocol {
4747
// Return an effect that re-increments the count after 1 second if the count is negative
4848
return state.count >= 0
4949
? .none
50-
: .task {
50+
: .run { send in
5151
try await self.clock.sleep(for: .seconds(1))
52-
return .decrementDelayResponse
52+
await send(.decrementDelayResponse)
5353
}
5454
.cancellable(id: CancelID.delay)
5555

@@ -71,8 +71,8 @@ struct EffectsBasics: ReducerProtocol {
7171
state.numberFact = nil
7272
// Return an effect that fetches a number fact from the API and returns the
7373
// value back to the reducer's `numberFactResponse` action.
74-
return .task { [count = state.count] in
75-
await .numberFactResponse(TaskResult { try await self.factClient.fetch(count) })
74+
return .run { [count = state.count] send in
75+
await send(.numberFactResponse(TaskResult { try await self.factClient.fetch(count) }))
7676
}
7777

7878
case let .numberFactResponse(.success(response)):

Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Cancellation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ struct EffectsCancellation: ReducerProtocol {
4747
state.currentFact = nil
4848
state.isFactRequestInFlight = true
4949

50-
return .task { [count = state.count] in
51-
await .factResponse(TaskResult { try await self.factClient.fetch(count) })
50+
return .run { [count = state.count] send in
51+
await send(.factResponse(TaskResult { try await self.factClient.fetch(count) }))
5252
}
5353
.cancellable(id: CancelID.factRequest)
5454

Examples/CaseStudies/SwiftUICaseStudies/02-Effects-Refreshable.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import ComposableArchitecture
2-
import SwiftUI
2+
@preconcurrency import SwiftUI
33

44
private let readMe = """
55
This application demonstrates how to make use of SwiftUI's `refreshable` API in the Composable \
@@ -53,10 +53,12 @@ struct Refreshable: ReducerProtocol {
5353

5454
case .refresh:
5555
state.fact = nil
56-
return .task { [count = state.count] in
57-
await .factResponse(TaskResult { try await self.factClient.fetch(count) })
56+
return .run { [count = state.count] send in
57+
await send(
58+
.factResponse(TaskResult { try await self.factClient.fetch(count) }),
59+
animation: .default
60+
)
5861
}
59-
.animation()
6062
.cancellable(id: CancelID.factRequest)
6163
}
6264
}

Examples/CaseStudies/SwiftUICaseStudies/02-Effects-WebSocket.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ struct WebSocket: ReducerProtocol {
101101
case .sendButtonTapped:
102102
let messageToSend = state.messageToSend
103103
state.messageToSend = ""
104-
return .task {
104+
return .run { send in
105105
try await self.webSocket.send(WebSocketClient.ID(), .string(messageToSend))
106-
return .sendResponse(didSucceed: true)
107-
} catch: { _ in
108-
.sendResponse(didSucceed: false)
106+
await send(.sendResponse(didSucceed: true))
107+
} catch: { _, send in
108+
await send(.sendResponse(didSucceed: false))
109109
}
110110
.cancellable(id: WebSocketClient.ID())
111111

Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Lists-LoadThenNavigate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ struct LoadThenNavigateList: ReducerProtocol {
5050
for row in state.rows {
5151
state.rows[id: row.id]?.isActivityIndicatorVisible = row.id == navigatedId
5252
}
53-
return .task {
53+
return .run { send in
5454
try await self.clock.sleep(for: .seconds(1))
55-
return .setNavigationSelectionDelayCompleted(navigatedId)
55+
await send(.setNavigationSelectionDelayCompleted(navigatedId))
5656
}
5757
.cancellable(id: CancelID.load, cancelInFlight: true)
5858

Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Lists-NavigateAndLoad.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ struct NavigateAndLoadList: ReducerProtocol {
4242

4343
case let .setNavigation(selection: .some(id)):
4444
state.selection = Identified(nil, id: id)
45-
return .task {
45+
return .run { send in
4646
try await self.clock.sleep(for: .seconds(1))
47-
return .setNavigationSelectionDelayCompleted
47+
await send(.setNavigationSelectionDelayCompleted)
4848
}
4949
.cancellable(id: CancelID.load, cancelInFlight: true)
5050

Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-LoadThenNavigate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ struct LoadThenNavigate: ReducerProtocol {
3737

3838
case .setNavigation(isActive: true):
3939
state.isActivityIndicatorVisible = true
40-
return .task {
40+
return .run { send in
4141
try await self.clock.sleep(for: .seconds(1))
42-
return .setNavigationIsActiveDelayCompleted
42+
await send(.setNavigationIsActiveDelayCompleted)
4343
}
4444
.cancellable(id: CancelID.load)
4545

Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-NavigateAndLoad.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ struct NavigateAndLoad: ReducerProtocol {
3030
switch action {
3131
case .setNavigation(isActive: true):
3232
state.isNavigationActive = true
33-
return .task {
33+
return .run { send in
3434
try await self.clock.sleep(for: .seconds(1))
35-
return .setNavigationIsActiveDelayCompleted
35+
await send(.setNavigationIsActiveDelayCompleted)
3636
}
3737
.cancellable(id: CancelID.load)
3838

Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Sheet-LoadThenPresent.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ struct LoadThenPresent: ReducerProtocol {
3737

3838
case .setSheet(isPresented: true):
3939
state.isActivityIndicatorVisible = true
40-
return .task {
40+
return .run { send in
4141
try await self.clock.sleep(for: .seconds(1))
42-
return .setSheetIsPresentedDelayCompleted
42+
await send(.setSheetIsPresentedDelayCompleted)
4343
}
4444
.cancellable(id: CancelID.load)
4545

Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Sheet-PresentAndLoad.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ struct PresentAndLoad: ReducerProtocol {
3030
switch action {
3131
case .setSheet(isPresented: true):
3232
state.isSheetPresented = true
33-
return .task {
33+
return .run { send in
3434
try await self.clock.sleep(for: .seconds(1))
35-
return .setSheetIsPresentedDelayCompleted
35+
await send(.setSheetIsPresentedDelayCompleted)
3636
}
3737
.cancellable(id: CancelID.load)
3838

Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ReusableFavoriting.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ struct Favoriting<ID: Hashable & Sendable>: ReducerProtocol {
5050
case .buttonTapped:
5151
state.isFavorite.toggle()
5252

53-
return .task { [id = state.id, isFavorite = state.isFavorite, favorite] in
54-
await .response(TaskResult { try await favorite(id, isFavorite) })
53+
return .run { [id = state.id, isFavorite = state.isFavorite, favorite] send in
54+
await send(.response(TaskResult { try await favorite(id, isFavorite) }))
5555
}
5656
.cancellable(id: CancelID(id: state.id), cancelInFlight: true)
5757

Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-LongLivingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import XCTest
66
@MainActor
77
final class LongLivingEffectsTests: XCTestCase {
88
func testReducer() async {
9-
let (screenshots, takeScreenshot) = AsyncStream<Void>.streamWithContinuation()
9+
let (screenshots, takeScreenshot) = AsyncStream.makeStream(of: Void.self)
1010

1111
let store = TestStore(initialState: LongLivingEffects.State()) {
1212
LongLivingEffects()

Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-WebSocketTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import XCTest
66
@MainActor
77
final class WebSocketTests: XCTestCase {
88
func testWebSocketHappyPath() async {
9-
let actions = AsyncStream<WebSocketClient.Action>.streamWithContinuation()
10-
let messages = AsyncStream<TaskResult<WebSocketClient.Message>>.streamWithContinuation()
9+
let actions = AsyncStream.makeStream(of: WebSocketClient.Action.self)
10+
let messages = AsyncStream.makeStream(of: TaskResult<WebSocketClient.Message>.self)
1111

1212
let store = TestStore(initialState: WebSocket.State()) {
1313
WebSocket()
@@ -57,8 +57,8 @@ final class WebSocketTests: XCTestCase {
5757
}
5858

5959
func testWebSocketSendFailure() async {
60-
let actions = AsyncStream<WebSocketClient.Action>.streamWithContinuation()
61-
let messages = AsyncStream<TaskResult<WebSocketClient.Message>>.streamWithContinuation()
60+
let actions = AsyncStream.makeStream(of: WebSocketClient.Action.self)
61+
let messages = AsyncStream.makeStream(of: TaskResult<WebSocketClient.Message>.self)
6262

6363
let store = TestStore(initialState: WebSocket.State()) {
6464
WebSocket()
@@ -103,7 +103,7 @@ final class WebSocketTests: XCTestCase {
103103
}
104104

105105
func testWebSocketPings() async {
106-
let actions = AsyncStream<WebSocketClient.Action>.streamWithContinuation()
106+
let actions = AsyncStream.makeStream(of: WebSocketClient.Action.self)
107107
let clock = TestClock()
108108
var pingsCount = 0
109109

@@ -137,7 +137,7 @@ final class WebSocketTests: XCTestCase {
137137
}
138138

139139
func testWebSocketConnectError() async {
140-
let actions = AsyncStream<WebSocketClient.Action>.streamWithContinuation()
140+
let actions = AsyncStream.makeStream(of: WebSocketClient.Action.self)
141141

142142
let store = TestStore(initialState: WebSocket.State()) {
143143
WebSocket()

Examples/CaseStudies/SwiftUICaseStudiesTests/04-HigherOrderReducers-ReusableOfflineDownloadsTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import XCTest
55

66
@MainActor
77
final class ReusableComponentsDownloadComponentTests: XCTestCase {
8-
let download = AsyncThrowingStream<DownloadClient.Event, Error>.streamWithContinuation()
8+
let download = AsyncThrowingStream.makeStream(of: DownloadClient.Event.self)
99

1010
func testDownloadFlow() async {
1111
let store = TestStore(

Examples/CaseStudies/UIKitCaseStudies/LoadThenNavigate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ struct LazyNavigation: ReducerProtocol {
2727

2828
case .setNavigation(isActive: true):
2929
state.isActivityIndicatorHidden = false
30-
return .task {
30+
return .run { send in
3131
try await self.clock.sleep(for: .seconds(1))
32-
return .setNavigationIsActiveDelayCompleted
32+
await send(.setNavigationIsActiveDelayCompleted)
3333
}
3434
.cancellable(id: CancelID.load)
3535

Examples/CaseStudies/UIKitCaseStudies/NavigateAndLoad.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ struct EagerNavigation: ReducerProtocol {
2323
switch action {
2424
case .setNavigation(isActive: true):
2525
state.isNavigationActive = true
26-
return .task {
26+
return .run { send in
2727
try await self.clock.sleep(for: .seconds(1))
28-
return .setNavigationIsActiveDelayCompleted
28+
await send(.setNavigationIsActiveDelayCompleted)
2929
}
3030
.cancellable(id: CancelID.load)
3131

Examples/Search/Search/SearchView.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ struct Search: ReducerProtocol {
8080
guard !state.searchQuery.isEmpty else {
8181
return .none
8282
}
83-
return .task { [query = state.searchQuery] in
84-
await .searchResponse(TaskResult { try await self.weatherClient.search(query) })
83+
return .run { [query = state.searchQuery] send in
84+
await send(.searchResponse(TaskResult { try await self.weatherClient.search(query) }))
8585
}
8686
.cancellable(id: CancelID.location)
8787

@@ -96,10 +96,12 @@ struct Search: ReducerProtocol {
9696
case let .searchResultTapped(location):
9797
state.resultForecastRequestInFlight = location
9898

99-
return .task {
100-
await .forecastResponse(
101-
location.id,
102-
TaskResult { try await self.weatherClient.forecast(location) }
99+
return .run { send in
100+
await send(
101+
.forecastResponse(
102+
location.id,
103+
TaskResult { try await self.weatherClient.forecast(location) }
104+
)
103105
)
104106
}
105107
.cancellable(id: CancelID.weather, cancelInFlight: true)

Examples/SpeechRecognition/SpeechRecognition/SpeechRecognition.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct SpeechRecognition: ReducerProtocol {
3535

3636
guard state.isRecording
3737
else {
38-
return .fireAndForget {
38+
return .run { _ in
3939
await self.speechClient.finishTask()
4040
}
4141
}

Examples/SpeechRecognition/SpeechRecognitionTests/SpeechRecognitionTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import XCTest
55

66
@MainActor
77
final class SpeechRecognitionTests: XCTestCase {
8-
let recognitionTask = AsyncThrowingStream<SpeechRecognitionResult, Error>.streamWithContinuation()
8+
let recognitionTask = AsyncThrowingStream.makeStream(of: SpeechRecognitionResult.self)
99

1010
func testDenyAuthorization() async {
1111
let store = TestStore(initialState: SpeechRecognition.State()) {

Examples/TicTacToe/tic-tac-toe/Sources/LoginCore/LoginCore.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public struct Login: ReducerProtocol, Sendable {
1515
public init() {}
1616
}
1717

18-
public enum Action: Equatable {
18+
public enum Action: Equatable, Sendable {
1919
case alertDismissed
2020
case emailChanged(String)
2121
case passwordChanged(String)
@@ -60,13 +60,15 @@ public struct Login: ReducerProtocol, Sendable {
6060

6161
case .loginButtonTapped:
6262
state.isLoginRequestInFlight = true
63-
return .task { [email = state.email, password = state.password] in
64-
.loginResponse(
65-
await TaskResult {
66-
try await self.authenticationClient.login(
67-
.init(email: email, password: password)
68-
)
69-
}
63+
return .run { [email = state.email, password = state.password] send in
64+
await send(
65+
.loginResponse(
66+
await TaskResult {
67+
try await self.authenticationClient.login(
68+
.init(email: email, password: password)
69+
)
70+
}
71+
)
7072
)
7173
}
7274

@@ -75,7 +77,7 @@ public struct Login: ReducerProtocol, Sendable {
7577

7678
case .twoFactorDismissed:
7779
state.twoFactor = nil
78-
return .cancel(id: TwoFactor.TearDownToken.self)
80+
return .cancel(id: TwoFactor.CancelID.tearDown)
7981
}
8082
}
8183
.ifLet(\.twoFactor, action: /Action.twoFactor) {

Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorCore/TwoFactorCore.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ public struct TwoFactor: ReducerProtocol, Sendable {
1616
}
1717
}
1818

19-
public enum Action: Equatable {
19+
public enum Action: Equatable, Sendable {
2020
case alertDismissed
2121
case codeChanged(String)
2222
case submitButtonTapped
2323
case twoFactorResponse(TaskResult<AuthenticationResponse>)
2424
}
2525

26-
public enum TearDownToken {}
26+
public enum CancelID { case tearDown }
2727

2828
@Dependency(\.authenticationClient) var authenticationClient
2929

@@ -42,14 +42,16 @@ public struct TwoFactor: ReducerProtocol, Sendable {
4242

4343
case .submitButtonTapped:
4444
state.isTwoFactorRequestInFlight = true
45-
return .task { [code = state.code, token = state.token] in
46-
.twoFactorResponse(
47-
await TaskResult {
48-
try await self.authenticationClient.twoFactor(.init(code: code, token: token))
49-
}
45+
return .run { [code = state.code, token = state.token] send in
46+
await send(
47+
.twoFactorResponse(
48+
TaskResult {
49+
try await self.authenticationClient.twoFactor(.init(code: code, token: token))
50+
}
51+
)
5052
)
5153
}
52-
.cancellable(id: TearDownToken.self)
54+
.cancellable(id: CancelID.tearDown)
5355

5456
case let .twoFactorResponse(.failure(error)):
5557
state.alert = AlertState { TextState(error.localizedDescription) }

0 commit comments

Comments
 (0)