Skip to content

Commit dd1fe6f

Browse files
committed
Add support for naming tasks in .run effects
1 parent f080048 commit dd1fe6f

File tree

8 files changed

+75
-32
lines changed

8 files changed

+75
-32
lines changed

Sources/ComposableArchitecture/Core.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ final class RootCore<Root: Reducer>: Core {
136136
task.cancel()
137137
}
138138
}
139-
case let .run(priority, operation):
139+
case let .run(priority, name, operation):
140140
withEscapedDependencies { continuation in
141-
let task = Task(priority: priority) { @MainActor [weak self] in
141+
let task = Task(name: name, priority: priority) { @MainActor [weak self] in
142142
let isCompleted = LockIsolated(false)
143143
defer { isCompleted.setValue(true) }
144144
await operation(

Sources/ComposableArchitecture/Effect.swift

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ public struct Effect<Action>: Sendable {
77
enum Operation: Sendable {
88
case none
99
case publisher(AnyPublisher<Action, Never>)
10-
case run(TaskPriority? = nil, @Sendable (_ send: Send<Action>) async -> Void)
10+
case run(
11+
TaskPriority? = nil,
12+
_ name: String? = nil,
13+
@Sendable (_ send: Send<Action>) async -> Void
14+
)
1115
}
1216

1317
@usableFromInline
@@ -76,6 +80,7 @@ extension Effect {
7680
/// - Parameters:
7781
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
7882
/// `Task.currentPriority`.
83+
/// - name: An optional name to associate with the task that runs this effect.
7984
/// - operation: The operation to execute.
8085
/// - handler: An error handler, invoked if the operation throws an error other than
8186
/// `CancellationError`.
@@ -86,6 +91,7 @@ extension Effect {
8691
/// - Returns: An effect wrapping the given asynchronous work.
8792
public static func run(
8893
priority: TaskPriority? = nil,
94+
name: String? = nil,
8995
operation: @escaping @Sendable (_ send: Send<Action>) async throws -> Void,
9096
catch handler: (@Sendable (_ error: any Error, _ send: Send<Action>) async -> Void)? = nil,
9197
fileID: StaticString = #fileID,
@@ -95,7 +101,7 @@ extension Effect {
95101
) -> Self {
96102
withEscapedDependencies { escaped in
97103
Self(
98-
operation: .run(priority) { send in
104+
operation: .run(priority, name) { send in
99105
await escaped.yield {
100106
do {
101107
try await operation(send)
@@ -268,14 +274,14 @@ extension Effect {
268274
.eraseToAnyPublisher()
269275
)
270276
)
271-
case let (.run(lhsPriority, lhsOperation), .run(rhsPriority, rhsOperation)):
277+
case let (.run(lhsPriority, lhsName, lhsOperation), .run(rhsPriority, rhsName, rhsOperation)):
272278
return Self(
273279
operation: .run { send in
274280
await withTaskGroup(of: Void.self) { group in
275-
group.addTask(priority: lhsPriority) {
281+
group.addTask(name: lhsName, priority: lhsPriority) {
276282
await lhsOperation(send)
277283
}
278-
group.addTask(priority: rhsPriority) {
284+
group.addTask(name: rhsName, priority: rhsPriority) {
279285
await rhsOperation(send)
280286
}
281287
}
@@ -328,16 +334,16 @@ extension Effect {
328334
.eraseToAnyPublisher()
329335
)
330336
)
331-
case let (.run(lhsPriority, lhsOperation), .run(rhsPriority, rhsOperation)):
337+
case let (.run(lhsPriority, lhsName, lhsOperation), .run(rhsPriority, rhsName, rhsOperation)):
332338
return Self(
333339
operation: .run { send in
334340
if let lhsPriority {
335-
await Task(priority: lhsPriority) { await lhsOperation(send) }.cancellableValue
341+
await Task(name: lhsName, priority: lhsPriority) { await lhsOperation(send) }.cancellableValue
336342
} else {
337343
await lhsOperation(send)
338344
}
339345
if let rhsPriority {
340-
await Task(priority: rhsPriority) { await rhsOperation(send) }.cancellableValue
346+
await Task(name: rhsName, priority: rhsPriority) { await rhsOperation(send) }.cancellableValue
341347
} else {
342348
await rhsOperation(send)
343349
}
@@ -372,10 +378,10 @@ extension Effect {
372378
.eraseToAnyPublisher()
373379
)
374380
)
375-
case let .run(priority, operation):
381+
case let .run(priority, name, operation):
376382
return withEscapedDependencies { escaped in
377383
.init(
378-
operation: .run(priority) { send in
384+
operation: .run(priority, name) { send in
379385
await escaped.yield {
380386
await operation(
381387
Send { action in
@@ -389,3 +395,40 @@ extension Effect {
389395
}
390396
}
391397
}
398+
399+
#if !swift(>=6.2)
400+
public extension Task {
401+
/// Backwards compatibility shim for named Task initializers introduced in Swift 6.2
402+
///
403+
/// This extension provides the `name` parameter for Task initializers on Swift versions
404+
/// prior to 6.2.
405+
///
406+
/// - Important: On Swift < 6.2, the `name` parameter is accepted but ignored.
407+
/// No naming functionality is provided - this is purely for API compatibility.
408+
///
409+
/// Example usage:
410+
/// ```swift
411+
/// let task = Task(name: "DataLoader") {
412+
/// await loadUserData()
413+
/// }
414+
/// ```
415+
@discardableResult
416+
init(
417+
name: String,
418+
priority: TaskPriority? = nil,
419+
operation: @escaping @Sendable () async -> Success
420+
) where Failure == Never {
421+
self.init(priority: priority, operation: operation)
422+
}
423+
424+
/// Backwards compatibility shim for named throwing Task initializers
425+
@discardableResult
426+
init(
427+
name: String,
428+
priority: TaskPriority? = nil,
429+
operation: @escaping @Sendable () async throws -> Success
430+
) where Failure == Error {
431+
self.init(priority: priority, operation: operation)
432+
}
433+
}
434+
#endif

Sources/ComposableArchitecture/Effects/Animation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ extension Effect {
4242
TransactionPublisher(upstream: publisher, transaction: transaction).eraseToAnyPublisher()
4343
)
4444
)
45-
case let .run(priority, operation):
45+
case let .run(priority, _, operation):
4646
let uncheckedTransaction = UncheckedSendable(transaction)
4747
return Self(
4848
operation: .run(priority) { send in

Sources/ComposableArchitecture/Effects/Cancellation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ extension Effect {
8383
.eraseToAnyPublisher()
8484
)
8585
)
86-
case let .run(priority, operation):
86+
case let .run(priority, name, operation):
8787
return withEscapedDependencies { continuation in
8888
return Self(
89-
operation: .run(priority) { send in
89+
operation: .run(priority, name) { send in
9090
await continuation.yield {
9191
await withTaskCancellation(id: id, cancelInFlight: cancelInFlight) {
9292
await operation(send)

Sources/ComposableArchitecture/Effects/Publisher.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public struct _EffectPublisher<Action>: Publisher {
3030
return Empty().eraseToAnyPublisher()
3131
case let .publisher(publisher):
3232
return publisher
33-
case let .run(priority, operation):
33+
case let .run(priority, name, operation):
3434
return .create { subscriber in
35-
let task = Task(priority: priority) { @MainActor in
35+
let task = Task(name: name, priority: priority) { @MainActor in
3636
defer { subscriber.send(completion: .finished) }
3737
await operation(Send { subscriber.send($0) })
3838
}

Sources/ComposableArchitecture/Internal/EffectActions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ extension Effect where Action: Sendable {
1515
cancellable.cancel()
1616
}
1717
}
18-
case let .run(priority, operation):
18+
case let .run(priority, name, operation):
1919
return AsyncStream { continuation in
20-
let task = Task(priority: priority) {
20+
let task = Task(name: name, priority: priority) {
2121
await operation(Send { action in continuation.yield(action) })
2222
continuation.finish()
2323
}

Sources/ComposableArchitecture/Reducer/Reducers/SignpostReducer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ extension Effect {
114114
.eraseToAnyPublisher()
115115
)
116116
)
117-
case let .run(priority, operation):
117+
case let .run(priority, name, operation):
118118
return .init(
119-
operation: .run(priority) { send in
119+
operation: .run(priority, name) { send in
120120
os_signpost(
121121
.begin, log: log, name: "Effect", signpostID: sid, "%sStarted from %s", prefix,
122122
actionOutput

Tests/ComposableArchitectureTests/EffectOperationTests.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
effect = Effect<Int>.run { send in await send(42) }
1818
.merge(with: .none)
1919
switch effect.operation {
20-
case let .run(_, send):
20+
case let .run(_, _, send):
2121
await send(.init(send: { XCTAssertEqual($0, 42) }))
2222
default:
2323
XCTFail()
@@ -26,7 +26,7 @@
2626
effect = Effect<Int>.none
2727
.merge(with: .run { send in await send(42) })
2828
switch effect.operation {
29-
case let .run(_, send):
29+
case let .run(_, _, send):
3030
await send(.init(send: { XCTAssertEqual($0, 42) }))
3131
default:
3232
XCTFail()
@@ -35,7 +35,7 @@
3535
effect = Effect<Int>.run { await $0(42) }
3636
.merge(with: .none)
3737
switch effect.operation {
38-
case let .run(_, send):
38+
case let .run(_, _, send):
3939
await send(.init(send: { XCTAssertEqual($0, 42) }))
4040
default:
4141
XCTFail()
@@ -44,7 +44,7 @@
4444
effect = Effect<Int>.none
4545
.merge(with: .run { await $0(42) })
4646
switch effect.operation {
47-
case let .run(_, send):
47+
case let .run(_, _, send):
4848
await send(.init(send: { XCTAssertEqual($0, 42) }))
4949
default:
5050
XCTFail()
@@ -64,7 +64,7 @@
6464
effect = Effect<Int>.run { send in await send(42) }
6565
.concatenate(with: .none)
6666
switch effect.operation {
67-
case let .run(_, send):
67+
case let .run(_, _, send):
6868
await send(.init(send: { XCTAssertEqual($0, 42) }))
6969
default:
7070
XCTFail()
@@ -73,7 +73,7 @@
7373
effect = Effect<Int>.none
7474
.concatenate(with: .run { send in await send(42) })
7575
switch effect.operation {
76-
case let .run(_, send):
76+
case let .run(_, _, send):
7777
await send(.init(send: { XCTAssertEqual($0, 42) }))
7878
default:
7979
XCTFail()
@@ -82,7 +82,7 @@
8282
effect = Effect<Int>.run { send in await send(42) }
8383
.concatenate(with: .none)
8484
switch effect.operation {
85-
case let .run(_, send):
85+
case let .run(_, _, send):
8686
await send(.init(send: { XCTAssertEqual($0, 42) }))
8787
default:
8888
XCTFail()
@@ -91,7 +91,7 @@
9191
effect = Effect<Int>.none
9292
.concatenate(with: .run { send in await send(42) })
9393
switch effect.operation {
94-
case let .run(_, send):
94+
case let .run(_, _, send):
9595
await send(.init(send: { XCTAssertEqual($0, 42) }))
9696
default:
9797
XCTFail()
@@ -113,7 +113,7 @@
113113
}
114114
)
115115
switch effect.operation {
116-
case let .run(_, send):
116+
case let .run(_, _, send):
117117
await send(.init { values.append($0) })
118118
default:
119119
XCTFail()
@@ -129,7 +129,7 @@
129129
let effect = Effect<Int>.run { send in await send(42) }
130130
.concatenate(with: .run { send in await send(1729) })
131131
switch effect.operation {
132-
case let .run(_, send):
132+
case let .run(_, _, send):
133133
await send(.init(send: { values.append($0) }))
134134
default:
135135
XCTFail()
@@ -143,7 +143,7 @@
143143
.map { "\($0)" }
144144

145145
switch effect.operation {
146-
case let .run(_, send):
146+
case let .run(_, _, send):
147147
await send(.init(send: { XCTAssertEqual($0, "42") }))
148148
default:
149149
XCTFail()

0 commit comments

Comments
 (0)