Skip to content

Commit c3760f1

Browse files
committed
Refactor internals and add missing documentation
1 parent 02e735a commit c3760f1

5 files changed

Lines changed: 410 additions & 189 deletions

File tree

Sources/Processed/Loadable/LoadableSupport.swift

Lines changed: 73 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@ import SwiftUI
4444
///
4545
/// - Note: This is only meant to be used in classes.
4646
/// If you want to do this inside a SwiftUI view, please refer to the ``Processed/Loadable`` property wrapper.
47-
@MainActor public protocol LoadableSupport: AnyObject {
47+
public protocol LoadableSupport: AnyObject {
4848

4949
/// Cancels the task of an ongoing resource loading process.
5050
///
5151
/// - Note: You are responsible for cooperating with the task cancellation within the loading closures.
52-
func cancel<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>)
53-
52+
@MainActor func cancel<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>)
53+
5454
/// Cancels the task of an ongoing resource loading process and resets the state to `.absent`.
5555
///
5656
/// - Note: You are responsible for cooperating with the task cancellation within the loading closures.
57-
func reset<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>)
57+
@MainActor func reset<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>)
5858

5959
/// Starts a resource loading process in a new `Task`, waiting for a return value or thrown error from the
6060
/// `block` closure, while setting the ``Processed/LoadableState`` accordingly.
@@ -72,8 +72,8 @@ import SwiftUI
7272
/// - block: The asynchronous block to run.
7373
///
7474
/// - Returns: The task that runs the asynchronous loading process. You don't have to store it, but you can.
75-
@discardableResult func load<Value>(
76-
_ loadableState: ReferenceWritableKeyPath<Self,LoadableState<Value>>,
75+
@MainActor @discardableResult func load<Value>(
76+
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
7777
silently runSilently: Bool,
7878
priority: TaskPriority?,
7979
block: @escaping () async throws -> Value
@@ -95,8 +95,8 @@ import SwiftUI
9595
/// - loadableState: The key path to the ``Processed/LoadableState``.
9696
/// - runSilently: If `true`, the state will not be set to `.loading` initially.
9797
/// - block: The asynchronous block to run.
98-
func load<Value>(
99-
_ loadableState: ReferenceWritableKeyPath<Self,LoadableState<Value>>,
98+
@MainActor func load<Value>(
99+
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
100100
silently runSilently: Bool,
101101
block: @escaping () async throws -> Value
102102
) async
@@ -117,8 +117,8 @@ import SwiftUI
117117
/// The block exposes a `yield` closure you can call to continuously update the resource loading state over time.
118118
///
119119
/// - Returns: The task that runs the asynchronous loading process. You don't have to store it, but you can.
120-
@discardableResult func load<Value>(
121-
_ loadableState: ReferenceWritableKeyPath<Self,LoadableState<Value>>,
120+
@MainActor @discardableResult func load<Value>(
121+
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
122122
silently runSilently: Bool,
123123
priority: TaskPriority?,
124124
block: @escaping (_ yield: (_ state: LoadableState<Value>) -> Void) async throws -> Void
@@ -139,72 +139,86 @@ import SwiftUI
139139
/// - runSilently: If `true`, the state will not be set to `.loading` initially.
140140
/// - block: The asynchronous block to run.
141141
/// The block exposes a `yield` closure you can call to continuously update the resource loading state over time.
142-
func load<Value>(
143-
_ loadableState: ReferenceWritableKeyPath<Self,LoadableState<Value>>,
142+
@MainActor func load<Value>(
143+
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
144144
silently runSilently: Bool,
145145
block: @escaping (_ yield: (_ state: LoadableState<Value>) -> Void) async throws -> Void
146146
) async
147147
}
148148

149149
extension LoadableSupport {
150150

151-
public func cancel<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>) {
152-
let identifier = ProcessIdentifier(
153-
identifier: ObjectIdentifier(self),
154-
keyPath: loadableState
155-
)
156-
tasks[identifier]?.cancel()
157-
tasks.removeValue(forKey: identifier)
151+
@MainActor public func cancel<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>) {
152+
let identifier = TaskStore.shared.identifier(for: loadableState, in: self)
153+
TaskStore.shared.tasks[identifier]?.cancel()
154+
TaskStore.shared.tasks.removeValue(forKey: identifier)
158155
}
159156

160-
public func reset<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>) {
157+
@MainActor public func reset<Value>(_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>) {
161158
if case .absent = self[keyPath: loadableState] {} else {
162159
self[keyPath: loadableState] = .absent
163160
}
164161
cancel(loadableState)
165162
}
166163

167-
@discardableResult public func load<Value>(
164+
@MainActor @discardableResult public func load<Value>(
168165
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
169166
silently runSilently: Bool = false,
170167
priority: TaskPriority? = nil,
171168
block: @escaping () async throws -> Value
172169
) -> Task<Void, Never> {
173-
let identifier = ProcessIdentifier(
174-
identifier: ObjectIdentifier(self),
175-
keyPath: loadableState
176-
)
177-
tasks[identifier]?.cancel()
178-
if !runSilently {
179-
if case .loading = self[keyPath: loadableState] {} else {
180-
self[keyPath: loadableState] = .loading
181-
}
170+
let identifier = TaskStore.shared.identifier(for: loadableState, in: self)
171+
TaskStore.shared.tasks[identifier]?.cancel()
172+
setLoadingStateIfNeeded(on: loadableState, runSilently: runSilently)
173+
TaskStore.shared.tasks[identifier] = Task(priority: priority) {
174+
defer { TaskStore.shared.tasks[identifier] = nil }
175+
await runReturningTaskBody(loadableState, silently: runSilently, block: block)
182176
}
183-
tasks[identifier] = Task(priority: priority) {
184-
defer { // Cleanup
185-
tasks[identifier] = nil
186-
}
187-
await load(
188-
loadableState,
189-
silently: runSilently,
190-
block: block
191-
)
177+
178+
return TaskStore.shared.tasks[identifier]!
179+
}
180+
181+
@MainActor public func load<Value>(
182+
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
183+
silently runSilently: Bool = false,
184+
block: @escaping () async throws -> Value
185+
) async {
186+
setLoadingStateIfNeeded(on: loadableState, runSilently: runSilently)
187+
await runReturningTaskBody(loadableState, silently: runSilently, block: block)
188+
}
189+
190+
@MainActor @discardableResult public func load<Value>(
191+
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
192+
silently runSilently: Bool = false,
193+
priority: TaskPriority? = nil,
194+
block: @escaping (_ yield: (_ state: LoadableState<Value>) -> Void) async throws -> Void
195+
) -> Task<Void, Never> {
196+
let identifier = TaskStore.shared.identifier(for: loadableState, in: self)
197+
TaskStore.shared.tasks[identifier]?.cancel()
198+
setLoadingStateIfNeeded(on: loadableState, runSilently: runSilently)
199+
TaskStore.shared.tasks[identifier] = Task(priority: priority) {
200+
defer { TaskStore.shared.tasks[identifier] = nil }
201+
await runYieldingTaskBody(loadableState, silently: runSilently, block: block)
192202
}
193203

194-
return tasks[identifier]!
204+
return TaskStore.shared.tasks[identifier]!
195205
}
196206

197-
public func load<Value>(
207+
@MainActor public func load<Value>(
208+
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
209+
silently runSilently: Bool = false,
210+
block: @escaping (_ yield: (_ state: LoadableState<Value>) -> Void) async throws -> Void
211+
) async {
212+
setLoadingStateIfNeeded(on: loadableState, runSilently: runSilently)
213+
await runYieldingTaskBody(loadableState, silently: runSilently, block: block)
214+
}
215+
216+
@MainActor private func runReturningTaskBody<Value>(
198217
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
199218
silently runSilently: Bool = false,
200219
block: @escaping () async throws -> Value
201220
) async {
202221
do {
203-
if !runSilently {
204-
if case .loading = self[keyPath: loadableState] {} else {
205-
self[keyPath: loadableState] = .loading
206-
}
207-
}
208222
self[keyPath: loadableState] = try await .loaded(block())
209223
} catch is CancellationError {
210224
// Task was cancelled. Don't change the state anymore
@@ -215,50 +229,13 @@ extension LoadableSupport {
215229
}
216230
}
217231

218-
@discardableResult public func load<Value>(
219-
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
220-
silently runSilently: Bool = false,
221-
priority: TaskPriority? = nil,
222-
block: @escaping (_ yield: (_ state: LoadableState<Value>) -> Void) async throws -> Void
223-
) -> Task<Void, Never> {
224-
let identifier = ProcessIdentifier(
225-
identifier: ObjectIdentifier(self),
226-
keyPath: loadableState
227-
)
228-
tasks[identifier]?.cancel()
229-
if !runSilently {
230-
if case .loading = self[keyPath: loadableState] {} else {
231-
self[keyPath: loadableState] = .loading
232-
}
233-
}
234-
tasks[identifier] = Task(priority: priority) {
235-
defer { // Cleanup
236-
tasks[identifier] = nil
237-
}
238-
await load(
239-
loadableState,
240-
silently: runSilently,
241-
block: block
242-
)
243-
}
244-
245-
return tasks[identifier]!
246-
}
247-
248-
public func load<Value>(
232+
@MainActor private func runYieldingTaskBody<Value>(
249233
_ loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
250234
silently runSilently: Bool = false,
251235
block: @escaping (_ yield: (_ state: LoadableState<Value>) -> Void) async throws -> Void
252236
) async {
253237
do {
254-
if !runSilently {
255-
if case .loading = self[keyPath: loadableState] {} else {
256-
self[keyPath: loadableState] = .loading
257-
}
258-
}
259-
try await block { state in
260-
self[keyPath: loadableState] = state
261-
}
238+
try await block { self[keyPath: loadableState] = $0 }
262239
} catch is CancellationError {
263240
// Task was cancelled. Don't change the state anymore
264241
} catch is CancelLoadable {
@@ -267,4 +244,15 @@ extension LoadableSupport {
267244
self[keyPath: loadableState] = .error(error)
268245
}
269246
}
247+
248+
@MainActor private func setLoadingStateIfNeeded<Value>(
249+
on loadableState: ReferenceWritableKeyPath<Self, LoadableState<Value>>,
250+
runSilently: Bool
251+
) {
252+
if !runSilently {
253+
if case .loading = self[keyPath: loadableState] {} else {
254+
self[keyPath: loadableState] = .loading
255+
}
256+
}
257+
}
270258
}

0 commit comments

Comments
 (0)