Skip to content

Commit 65043bb

Browse files
committed
Add new state property & stateUpdates AsyncStream to track a task's state
1 parent c878cc4 commit 65043bb

File tree

9 files changed

+547
-124
lines changed

9 files changed

+547
-124
lines changed

Sources/NetworkingClient/Tasks/DownloadTask.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ open class DownloadTask: NetworkTask<URL>, @unchecked Sendable {
2323
private let progressTracker = ProgressTracker()
2424

2525
/// A stream that emits progress updates throughout the download lifecycle.
26-
///
27-
/// The stream completes automatically when ``finish()`` is called.
28-
public var progressStream: AsyncStream<Double> {
26+
public var progressUpdates: AsyncStream<Double> {
2927
get async {
3028
return await progressTracker.progressStream
3129
}

Sources/NetworkingClient/Tasks/NetworkTask.swift

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ open class NetworkTask<T: Sendable>: @unchecked Sendable {
3030
private let session: Session
3131

3232
/// The thread safe state of the task.
33-
private let state: NetworkTaskState<Response>
33+
private let values: NetworkTaskValues<Response>
3434

3535
/// The underlying ``URLSessionTask``.
3636
internal var sessionTask: URLSessionTask? {
3737
get async {
38-
return await state.sessionTask
38+
return await values.sessionTask
3939
}
4040
}
4141

@@ -47,7 +47,7 @@ open class NetworkTask<T: Sendable>: @unchecked Sendable {
4747
/// You can use this to inspect the request metadata or re-execute it.
4848
public var request: AnyRequest {
4949
get async {
50-
return await state.request
50+
return await values.request
5151
}
5252
}
5353

@@ -59,7 +59,7 @@ open class NetworkTask<T: Sendable>: @unchecked Sendable {
5959
/// You can inspect this to view the final request sent over the network.
6060
public var urlRequest: URLRequest? {
6161
get async {
62-
return await state.urlRequest
62+
return await values.urlRequest
6363
}
6464
}
6565

@@ -75,7 +75,7 @@ open class NetworkTask<T: Sendable>: @unchecked Sendable {
7575
) {
7676
self.id = request.id
7777
self.session = session
78-
self.state = NetworkTaskState(request: request)
78+
self.values = NetworkTaskValues(request: request)
7979
}
8080

8181
// MARK: - Functions
@@ -97,7 +97,9 @@ open class NetworkTask<T: Sendable>: @unchecked Sendable {
9797
///
9898
/// - Note: This method is prefixed with `_` to indicate that it is not intended for public use.
9999
open func _finished(with error: NetworkingError?) async {
100-
if let urlRequest = await state.urlRequest {
100+
await values.transition(to: .completed)
101+
await values.finish()
102+
if let urlRequest = await values.urlRequest {
101103
await configurations.tasks.remove(urlRequest)
102104
}
103105
}
@@ -106,6 +108,10 @@ open class NetworkTask<T: Sendable>: @unchecked Sendable {
106108
///
107109
/// - Throws: A ``NetworkingError`` if request construction fails.
108110
@discardableResult public func response() async throws(NetworkingError) -> sending Response {
111+
guard await state != .cancelled else {
112+
throw .cancellation
113+
}
114+
await values.transition(to: .running)
109115
return try await currentTask().typedValue
110116
}
111117
}
@@ -116,7 +122,7 @@ extension NetworkTask {
116122
///
117123
/// - Returns: A task representing the lifecycle of the current request.
118124
private func currentTask() async -> Task<Response, any Error> {
119-
return await state.activeTask {
125+
return await values.activeTask {
120126
let result = await self.perform()
121127
await self._finished(with: result.error)
122128
return try result.get()
@@ -146,13 +152,15 @@ extension NetworkTask {
146152
result = .failure(error)
147153
}
148154

155+
await values.transition(to: .intercepting)
156+
149157
do throws(NetworkingError) {
150158
try Task.checkTypedCancellation()
151159
let context = await InterceptorContext(
152160
configurations: configurations,
153161
status: result.value?.1.status,
154162
retryCount: retryCount,
155-
urlRequest: state.urlRequest,
163+
urlRequest: values.urlRequest,
156164
error: result.error
157165
)
158166
let intercepted = try await configurations.taskInterceptor.intercept(
@@ -166,7 +174,7 @@ extension NetworkTask {
166174
case .failure(let error):
167175
return .failure(error.networkingError)
168176
case .retry:
169-
await state.resetTask()
177+
await values.resetTask()
170178
return await perform()
171179
}
172180
}catch {
@@ -185,7 +193,7 @@ extension NetworkTask {
185193
/// - Returns: A fully constructed and intercepted ``URLRequest``.
186194
/// - Throws: A ``NetworkingError`` if request construction fails.
187195
private func makeURLRequest() async throws(NetworkingError) -> URLRequest {
188-
if let oldRequest = await state.urlRequest {
196+
if let oldRequest = await values.urlRequest {
189197
await configurations.tasks.remove(oldRequest)
190198
}
191199
var urlRequest = try await request._makeURLRequest(with: configurations)
@@ -196,7 +204,7 @@ extension NetworkTask {
196204
with: configurations
197205
)
198206
await configurations.tasks.add(self, for: urlRequest)
199-
await state.set(urlRequest)
207+
await values.set(urlRequest)
200208
return urlRequest
201209
}
202210
}
@@ -233,14 +241,28 @@ extension NetworkTask: NetworkingTask {
233241
/// The number of retry attempts made for this task.
234242
public var retryCount: Int {
235243
get async {
236-
return await state.retryCount
244+
return await values.retryCount
237245
}
238246
}
239247

240248
/// The current task metrics.
241249
public var metrics: URLSessionTaskMetrics? {
242250
get async {
243-
return await state.metrics
251+
return await values.metrics
252+
}
253+
}
254+
255+
/// The current execution state of a task.
256+
public var state: TaskState {
257+
get async {
258+
return await values.state
259+
}
260+
}
261+
262+
/// A stream that emits state updates throughout the task lifecycle.
263+
public var stateUpdates: AsyncStream<TaskState> {
264+
get async {
265+
return await values.stateStream
244266
}
245267
}
246268

@@ -250,37 +272,46 @@ extension NetworkTask: NetworkingTask {
250272
///
251273
/// - Note: This method is prefixed with `_` to indicate that it is not intended for public use.
252274
public func _session(collected metrics: URLSessionTaskMetrics) async {
253-
await state.set(metrics)
275+
await values.set(metrics)
254276
}
255277

256278
/// Sets the ``URLSessionTask``.
257279
///
258280
/// - Note: This method is prefixed with `_` to indicate that it is not intended for public use.
259281
@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, macCatalyst 16.0, *)
260282
public func _set(_ task: URLSessionTask) async {
261-
await state.set(task)
283+
await values.set(task)
262284
}
263285

264286
/// Cancels the current task if any is running.
265287
@discardableResult public func cancel() async -> Self {
266-
await state.currentTask?.cancel()
288+
guard await values.transition(to: .cancelled) else {
289+
return self
290+
}
291+
await values.currentTask?.cancel()
267292
return self
268293
}
269294

270295
/// Resumes the task by starting it or continuing it if already started.
271296
@discardableResult public func resume() async -> Self {
272-
if await state.currentTask == nil {
297+
guard await values.transition(to: .running) else {
298+
return self
299+
}
300+
if await values.currentTask == nil {
273301
_ = await currentTask()
274302
}else {
275-
await state.sessionTask?.resume()
303+
await values.sessionTask?.resume()
276304
}
277305
return self
278306
}
279307

280308
/// Suspends the task if it's currently running.
281309
@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, visionOS 1.0, macCatalyst 16.0, *)
282310
@discardableResult public func suspend() async -> Self {
283-
await state.sessionTask?.suspend()
311+
guard await values.transition(to: .suspended) else {
312+
return self
313+
}
314+
await values.sessionTask?.suspend()
284315
return self
285316
}
286317
}

Sources/NetworkingClient/Tasks/NetworkTaskState.swift

Lines changed: 0 additions & 98 deletions
This file was deleted.

0 commit comments

Comments
 (0)