Skip to content

Commit fbc6b47

Browse files
committed
refactor
1 parent 39fb823 commit fbc6b47

File tree

8 files changed

+121
-172
lines changed

8 files changed

+121
-172
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,6 @@ fastlane/test_output
8787
# After new code Injection tools there's a generated folder /iOSInjectionProject
8888
# https://github.com/johnno1962/injectionforxcode
8989

90-
iOSInjectionProject/
90+
iOSInjectionProject/
91+
Sources/.DS_Store
92+
.DS_Store

Package.resolved

-9
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,6 @@
1818
"version" : "1.2.0"
1919
}
2020
},
21-
{
22-
"identity" : "swift-async-algorithms",
23-
"kind" : "remoteSourceControl",
24-
"location" : "https://github.com/apple/swift-async-algorithms",
25-
"state" : {
26-
"revision" : "cb417003f962f9de3fc7852c1b735a1f1152a89a",
27-
"version" : "1.0.0-beta.1"
28-
}
29-
},
3021
{
3122
"identity" : "swift-collections",
3223
"kind" : "remoteSourceControl",

Package.swift

-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ let package = Package(
1111
],
1212
dependencies: [
1313
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),
14-
.package(url: "https://github.com/apple/swift-async-algorithms", from: "1.0.0-beta.1"),
1514
.package(url: "https://github.com/adam-fowler/async-collections", from: "0.0.1"),
1615
],
1716
targets: [
1817
.target(
1918
name: "DataLoader",
2019
dependencies: [
2120
.product(name: "Algorithms", package: "swift-algorithms"),
22-
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
2321
.product(name: "AsyncCollections", package: "async-collections"),
2422
]
2523
),
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
public actor Channel<Success: Sendable, Failure: Error>: Sendable {
2+
private var state = State<Success, Failure>()
3+
}
4+
5+
public extension Channel {
6+
@discardableResult
7+
func fulfill(_ value: Success) async -> Bool {
8+
if await state.result == nil {
9+
await state.setResult(result: value)
10+
11+
for waiters in await state.waiters {
12+
waiters.resume(returning: value)
13+
}
14+
15+
await state.removeAllWaiters()
16+
17+
return false
18+
}
19+
20+
return true
21+
}
22+
23+
@discardableResult
24+
func fail(_ failure: Failure) async -> Bool {
25+
if await state.failure == nil {
26+
await state.setFailure(failure: failure)
27+
28+
for waiters in await state.waiters {
29+
waiters.resume(throwing: failure)
30+
}
31+
32+
await state.removeAllWaiters()
33+
34+
return false
35+
}
36+
37+
return true
38+
}
39+
40+
var value: Success {
41+
get async throws {
42+
try await withCheckedThrowingContinuation { continuation in
43+
Task {
44+
if let result = await state.result {
45+
continuation.resume(returning: result)
46+
} else if let failure = await self.state.failure {
47+
continuation.resume(throwing: failure)
48+
} else {
49+
await state.appendWaiters(waiters: continuation)
50+
}
51+
}
52+
}
53+
}
54+
}
55+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
typealias Waiter<Success, Failure> = CheckedContinuation<Success, Error>
2+
3+
actor State<Success, Failure> {
4+
var waiters = [Waiter<Success, Failure>]()
5+
var result: Success?
6+
var failure: Failure?
7+
}
8+
9+
extension State {
10+
func setResult(result: Success) {
11+
self.result = result
12+
}
13+
14+
func setFailure(failure: Failure) {
15+
self.failure = failure
16+
}
17+
18+
func appendWaiters(waiters: Waiter<Success, Failure>...) {
19+
self.waiters.append(contentsOf: waiters)
20+
}
21+
22+
func removeAllWaiters() {
23+
waiters.removeAll()
24+
}
25+
}

Sources/DataLoader/DataLoader.swift

+21-105
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,11 @@
11
import Algorithms
2-
import AsyncAlgorithms
32
import AsyncCollections
43

54
public enum DataLoaderValue<T: Sendable>: Sendable {
65
case success(T)
76
case failure(Error)
87
}
98

10-
actor Concurrent<T> {
11-
var wrappedValue: T
12-
13-
func nonmutating<Returned>(_ action: (T) throws -> Returned) async rethrows -> Returned {
14-
try action(wrappedValue)
15-
}
16-
17-
func mutating<Returned>(_ action: (inout T) throws -> Returned) async rethrows -> Returned {
18-
try action(&wrappedValue)
19-
}
20-
21-
init(_ value: T) {
22-
wrappedValue = value
23-
}
24-
}
25-
269
public typealias BatchLoadFunction<Key: Hashable & Sendable, Value: Sendable> = @Sendable (_ keys: [Key]) async throws -> [DataLoaderValue<Value>]
2710
private typealias LoaderQueue<Key: Hashable & Sendable, Value: Sendable> = [(key: Key, channel: Channel<Value, Error>)]
2811

@@ -63,30 +46,34 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
6346

6447
if options.batchingEnabled {
6548
queue.append((key: key, channel: channel))
49+
6650
if let executionPeriod = options.executionPeriod, !dispatchScheduled {
6751
Task.detached {
6852
try await Task.sleep(nanoseconds: executionPeriod)
6953
try await self.execute()
7054
}
55+
7156
dispatchScheduled = true
7257
}
7358
} else {
7459
Task.detached {
7560
do {
7661
let results = try await self.batchLoadFunction([key])
62+
7763
if results.isEmpty {
78-
await channel.fail(with: DataLoaderError.noValueForKey("Did not return value for key: \(key)"))
64+
await channel.fail(DataLoaderError.noValueForKey("Did not return value for key: \(key)"))
7965
} else {
8066
let result = results[0]
67+
8168
switch result {
8269
case let .success(value):
83-
await channel.fulfill(with: value)
70+
await channel.fulfill(value)
8471
case let .failure(error):
85-
await channel.fail(with: error)
72+
await channel.fail(error)
8673
}
8774
}
8875
} catch {
89-
await channel.fail(with: error)
76+
await channel.fail(error)
9077
}
9178
}
9279
}
@@ -116,22 +103,23 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
116103
/// ```swift
117104
/// async let a = myLoader.load(key: "a")
118105
/// async let b = myLoader.load(key: "b")
119-
/// let aAndB = try await a + b
120106
/// ```
121107
public func loadMany(keys: [Key]) async throws -> [Value] {
122108
guard !keys.isEmpty else {
123109
return []
124110
}
125-
let futures = try await keys.concurrentMap { try await self.load(key: $0) }
126-
return futures
111+
112+
return try await keys.concurrentMap { try await self.load(key: $0) }
127113
}
128114

129115
/// Clears the value at `key` from the cache, if it exists. Returns itself for
130116
/// method chaining.
131117
@discardableResult
132118
public func clear(key: Key) -> DataLoader<Key, Value> {
133119
let cacheKey = options.cacheKeyFunction?(key) ?? key
120+
134121
cache.removeValue(forKey: cacheKey)
122+
135123
return self
136124
}
137125

@@ -141,6 +129,7 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
141129
@discardableResult
142130
public func clearAll() -> DataLoader<Key, Value> {
143131
cache.removeAll()
132+
144133
return self
145134
}
146135

@@ -152,8 +141,9 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
152141

153142
if cache[cacheKey] == nil {
154143
let channel = Channel<Value, Error>()
144+
155145
Task.detached {
156-
await channel.fulfill(with: value)
146+
await channel.fulfill(value)
157147
}
158148

159149
cache[cacheKey] = channel
@@ -165,7 +155,9 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
165155
public func execute() async throws {
166156
// Take the current loader queue, replacing it with an empty queue.
167157
let batch = queue
158+
168159
queue = []
160+
169161
if dispatchScheduled {
170162
dispatchScheduled = false
171163
}
@@ -196,6 +188,7 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
196188
// loaded queue.
197189
do {
198190
let values = try await batchLoadFunction(keys)
191+
199192
if values.count != keys.count {
200193
throw DataLoaderError.typeError("The function did not return an array of the same length as the array of keys. \nKeys count: \(keys.count)\nValues count: \(values.count)")
201194
}
@@ -205,9 +198,9 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
205198

206199
switch result {
207200
case let .failure(error):
208-
await entry.element.channel.fail(with: error)
201+
await entry.element.channel.fail(error)
209202
case let .success(value):
210-
await entry.element.channel.fulfill(with: value)
203+
await entry.element.channel.fulfill(value)
211204
}
212205
}
213206
} catch {
@@ -218,85 +211,8 @@ public actor DataLoader<Key: Hashable & Sendable, Value: Sendable> {
218211
private func failedExecution(batch: LoaderQueue<Key, Value>, error: Error) async {
219212
for (key, channel) in batch {
220213
_ = clear(key: key)
221-
await channel.fail(with: error)
222-
}
223-
}
224-
}
225-
226-
public actor Channel<Success: Sendable, Failure: Error>: Sendable {
227-
typealias Waiter = CheckedContinuation<Success, Error>
228-
229-
private actor State {
230-
var waiters = [Waiter]()
231-
var result: Success? = nil
232-
var failure: Failure? = nil
233-
234-
func setResult(result: Success) {
235-
self.result = result
236-
}
237214

238-
func setFailure(failure: Failure) {
239-
self.failure = failure
215+
await channel.fail(error)
240216
}
241-
242-
func appendWaiters(waiters: Waiter...) {
243-
self.waiters.append(contentsOf: waiters)
244-
}
245-
246-
func removeAllWaiters() {
247-
self.waiters.removeAll()
248-
}
249-
}
250-
251-
private var state = State()
252-
253-
public init(_ elementType: Success.Type = Success.self) {}
254-
255-
@discardableResult
256-
public func fulfill(with value: Success) async -> Bool {
257-
if await state.result == nil {
258-
await state.setResult(result:value)
259-
for waiters in await state.waiters {
260-
waiters.resume(returning: value)
261-
}
262-
await state.removeAllWaiters()
263-
return false
264-
}
265-
return true
266-
}
267-
268-
@discardableResult
269-
public func fail(with failure: Failure) async -> Bool {
270-
if await state.failure == nil {
271-
await state.setFailure(failure: failure)
272-
for waiters in await state.waiters {
273-
waiters.resume(throwing: failure)
274-
}
275-
await state.removeAllWaiters()
276-
return false
277-
}
278-
return true
279-
}
280-
281-
public var value: Success {
282-
get async throws {
283-
try await withCheckedThrowingContinuation { continuation in
284-
Task {
285-
if let result = await state.result {
286-
continuation.resume(returning: result)
287-
} else if let failure = await self.state.failure {
288-
continuation.resume(throwing: failure)
289-
} else {
290-
await state.appendWaiters(waiters: continuation)
291-
}
292-
}
293-
}
294-
}
295-
}
296-
}
297-
298-
extension Channel where Success == Void {
299-
func fulfill() async -> Bool {
300-
return await fulfill(with: ())
301217
}
302218
}

Tests/DataLoaderTests/DataLoaderAbuseTests.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ class DataLoaderAbuseTests: XCTestCase {
2525
}
2626

2727
func testBatchFuntionMustPromiseAnArrayOfCorrectLength() async {
28-
2928
let identityLoader = DataLoader<Int, Int>() { _ in
3029
[]
3130
}
@@ -95,7 +94,7 @@ class DataLoaderAbuseTests: XCTestCase {
9594

9695
async let value1 = identityLoader.load(key: 1)
9796
async let value2 = identityLoader.load(key: 2)
98-
97+
9998
var didFailWithError: Error?
10099

101100
do {

0 commit comments

Comments
 (0)