Skip to content

Commit 7febf51

Browse files
Updates documentation
1 parent a264c85 commit 7febf51

9 files changed

+150
-134
lines changed

.gitignore

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
#
33
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
44

5-
## Build generated
5+
## User settings
6+
xcuserdata/
7+
8+
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9+
*.xcscmblueprint
10+
*.xccheckout
11+
12+
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
613
build/
714
DerivedData/
8-
9-
## Various settings
15+
*.moved-aside
1016
*.pbxuser
1117
!default.pbxuser
1218
*.mode1v3
@@ -15,15 +21,11 @@ DerivedData/
1521
!default.mode2v3
1622
*.perspectivev3
1723
!default.perspectivev3
18-
xcuserdata/
19-
20-
## Other
21-
*.moved-aside
22-
*.xccheckout
23-
*.xcscmblueprint
2424

2525
## Obj-C/Swift specific
2626
*.hmap
27+
28+
## App packaging
2729
*.ipa
2830
*.dSYM.zip
2931
*.dSYM
@@ -38,11 +40,13 @@ playground.xcworkspace
3840
# Packages/
3941
# Package.pins
4042
# Package.resolved
41-
.DS_Store
42-
/.build
43-
/Packages
44-
/*.xcodeproj
43+
# *.xcodeproj
44+
#
45+
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46+
# hence it is not needed unless you have added a package configuration file to your project
47+
.swiftpm
4548

49+
.build/
4650

4751
# CocoaPods
4852
#
@@ -51,22 +55,36 @@ playground.xcworkspace
5155
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
5256
#
5357
# Pods/
58+
#
59+
# Add this line if you want to avoid checking in source code from the Xcode workspace
60+
# *.xcworkspace
5461

5562
# Carthage
5663
#
5764
# Add this line if you want to avoid checking in source code from Carthage dependencies.
5865
# Carthage/Checkouts
5966

60-
Carthage/Build
67+
Carthage/Build/
68+
69+
# Accio dependency management
70+
Dependencies/
71+
.accio/
6172

6273
# fastlane
6374
#
64-
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
65-
# screenshots whenever they are needed.
75+
# It is recommended to not store the screenshots in the git repo.
76+
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
6677
# For more information about the recommended setup visit:
6778
# https://docs.fastlane.tools/best-practices/source-control/#source-control
6879

6980
fastlane/report.xml
7081
fastlane/Preview.html
7182
fastlane/screenshots/**/*.png
7283
fastlane/test_output
84+
85+
# Code Injection
86+
#
87+
# After new code Injection tools there's a generated folder /iOSInjectionProject
88+
# https://github.com/johnno1962/injectionforxcode
89+
90+
iOSInjectionProject/

README.md

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ SwiftDataLoader is a generic utility to be used as part of your application's da
33

44
This is a Swift version of the Facebook [DataLoader](https://github.com/facebook/dataloader).
55

6-
[![CircleCI](https://circleci.com/gh/kimdv/SwiftDataLoader.svg?style=svg)](https://circleci.com/gh/kimdv/SwiftDataLoader)
7-
[![codecov](https://codecov.io/gh/kimdv/SwiftDataLoader/branch/master/graph/badge.svg)](https://codecov.io/gh/kimdv/SwiftDataLoader)
6+
[![Swift][swift-badge]][swift-url]
7+
[![License][mit-badge]][mit-url]
8+
[![CircleCI][circleci-badge]][circleci-uri]
9+
[![codecov][codecov-badge]][codecov-uri]
810

911
## Installation 💻
1012

1113
Update your `Package.swift` file.
1214

1315
```swift
14-
.Package(url: "https://github.com/kimdv/SwiftDataLoader.git", majorVersion: 1)
16+
.Package(url: "https://github.com/GraphQLSwift/SwiftDataLoader.git", .upToNextMajor(from: "1.1.0"))
1517
```
1618

1719
## Gettings started 🚀
@@ -27,27 +29,27 @@ let userLoader = Dataloader<Int, User>(batchLoadFunction: { keys in
2729
})
2830
```
2931
#### Load single key
30-
```
31-
let future1 = try userLoader.load(key: 1, on: req)
32-
let future2 = try userLoader.load(key: 2, on: req)
33-
let future3 = try userLoader.load(key: 1, on: req)
32+
```swift
33+
let future1 = try userLoader.load(key: 1, on: eventLoopGroup)
34+
let future2 = try userLoader.load(key: 2, on: eventLoopGroup)
35+
let future3 = try userLoader.load(key: 1, on: eventLoopGroup)
3436
```
3537

3638
Now there is only one thing left and that is to dispathc it `try userLoader.dispatchQueue(on: req.eventLoop)`
3739

3840
The example above will only fetch two users, because the user with key `1` is present twice in the list.
3941

4042
#### Load multiple keys
41-
There is also an API to load multiple keys at once
42-
```
43-
try userLoader.loadMany(keys: [1, 2, 3], on: req.eventLoop)
43+
There is also a method to load multiple keys at once
44+
```swift
45+
try userLoader.loadMany(keys: [1, 2, 3], on: eventLoopGroup)
4446
```
4547

46-
### Disable batching
47-
It is also possible to disable batching `DataLoaderOptions(batchingEnabled: false)`
48-
It will invoke `batchLoadFunction` with a single key
48+
#### Disable batching
49+
It is possible to disable batching `DataLoaderOptions(batchingEnabled: false)`
50+
It will invoke `batchLoadFunction` immediately whenever any key is loaded
4951

50-
### Chaching
52+
### Caching
5153

5254
DataLoader provides a memoization cache for all loads which occur in a single
5355
request to your application. After `.load()` is called once with a given key,
@@ -58,8 +60,8 @@ also creates fewer objects which may relieve memory pressure on your application
5860

5961
```swift
6062
let userLoader = DataLoader<Int, Int>(...)
61-
let future1 = userLoader.load(1)
62-
let future2 = userLoader.load(1)
63+
let future1 = userLoader.load(key: 1, on: eventLoopGroup)
64+
let future2 = userLoader.load(key: 1, on: eventLoopGroup)
6365
assert(future1 === future2)
6466
```
6567

@@ -91,13 +93,13 @@ Here's a simple example using SQL UPDATE to illustrate.
9193
let userLoader = DataLoader<Int, Int>(...)
9294

9395
// And a value happens to be loaded (and cached).
94-
userLoader.load(4)
96+
userLoader.load(key: 4, on: eventLoopGroup)
9597

9698
// A mutation occurs, invalidating what might be in cache.
9799
sqlRun('UPDATE users WHERE id=4 SET username="zuck"').then { userLoader.clear(4) }
98100

99101
// Later the value load is loaded again so the mutated data appears.
100-
userLoader.load(4)
102+
userLoader.load(key: 4, on: eventLoopGroup)
101103

102104
// Request completes.
103105
```
@@ -112,19 +114,19 @@ be cached to avoid frequently loading the same `Error`.
112114
In some circumstances you may wish to clear the cache for these individual Errors:
113115

114116
```swift
115-
userLoader.load(1).catch { error in {
117+
userLoader.load(key: 1, on: eventLoopGroup).catch { error in {
116118
if (/* determine if should clear error */) {
117-
userLoader.clear(1);
118-
}
119-
throw error
119+
userLoader.clear(key: 1);
120+
}
121+
throw error
120122
}
121123
```
122124

123125
#### Disabling Cache
124126

125127
In certain uncommon cases, a DataLoader which *does not* cache may be desirable.
126128
Calling `DataLoader(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: batchLoadFunction)` will ensure that every
127-
call to `.load()` will produce a *new* Future, and requested keys will not be
129+
call to `.load()` will produce a *new* Future, and previously requested keys will not be
128130
saved in memory.
129131

130132
However, when the memoization cache is disabled, your batch function will
@@ -135,13 +137,16 @@ for each instance of the requested key.
135137
For example:
136138

137139
```swift
138-
let myLoader = DataLoader<String, String>(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: { keys in
139-
self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) }
140-
})
140+
let myLoader = DataLoader<String, String>(
141+
options: DataLoaderOptions(cachingEnabled: false),
142+
batchLoadFunction: { keys in
143+
self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) }
144+
}
145+
)
141146

142-
myLoader.load("A")
143-
myLoader.load("B")
144-
myLoader.load("A")
147+
myLoader.load(key: "A", on: eventLoopGroup)
148+
myLoader.load(key: "B", on: eventLoopGroup)
149+
myLoader.load(key: "A", on: eventLoopGroup)
145150

146151
// > [ "A", "B", "A" ]
147152
```
@@ -171,3 +176,15 @@ When creating a pull request, please adhere to the current coding style where po
171176

172177
This library is entirely a Swift version of Facebooks [DataLoader](https://github.com/facebook/dataloader). Developed by [Lee Byron](https://github.com/leebyron) and
173178
[Nicholas Schrock](https://github.com/schrockn) from [Facebook](https://www.facebook.com/).
179+
180+
[swift-badge]: https://img.shields.io/badge/Swift-5.2-orange.svg?style=flat
181+
[swift-url]: https://swift.org
182+
183+
[mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat
184+
[mit-url]: https://tldrlegal.com/license/mit-license
185+
186+
[circleci-badge]: https://circleci.com/gh/kimdv/SwiftDataLoader.svg?style=svg
187+
[circleci-uri]: https://circleci.com/gh/kimdv/SwiftDataLoader
188+
189+
[codecov-badge]: https://codecov.io/gh/kimdv/SwiftDataLoader/branch/master/graph/badge.svg
190+
[codecov-uri]: https://codecov.io/gh/kimdv/SwiftDataLoader

Sources/SwiftDataLoader/DataLoader.swift

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
//
2-
// DataLoader.swift
3-
// App
4-
//
5-
// Created by Kim de Vos on 01/06/2018.
6-
//
71
import NIO
82

93
public enum DataLoaderFutureValue<T> {
@@ -12,10 +6,16 @@ public enum DataLoaderFutureValue<T> {
126
}
137

148
public typealias BatchLoadFunction<Key, Value> = (_ keys: [Key]) throws -> EventLoopFuture<[DataLoaderFutureValue<Value>]>
15-
16-
// Private
179
private typealias LoaderQueue<Key, Value> = Array<(key: Key, promise: EventLoopPromise<Value>)>
1810

11+
/// DataLoader creates a public API for loading data from a particular
12+
/// data back-end with unique keys such as the id column of a SQL table
13+
/// or document name in a MongoDB database, given a batch loading function.
14+
///
15+
/// Each DataLoader instance contains a unique memoized cache. Use caution
16+
/// when used in long-lived applications or those which serve many users
17+
/// with different access permissions and consider creating a new instance
18+
/// per data request.
1919
final public class DataLoader<Key: Hashable, Value> {
2020

2121
private let batchLoadFunction: BatchLoadFunction<Key, Value>
@@ -29,8 +29,7 @@ final public class DataLoader<Key: Hashable, Value> {
2929
self.batchLoadFunction = batchLoadFunction
3030
}
3131

32-
33-
/// Loads a key, returning a `Promise` for the value represented by that key.
32+
/// Loads a key, returning an `EventLoopFuture` for the value represented by that key.
3433
public func load(key: Key, on eventLoop: EventLoopGroup) throws -> EventLoopFuture<Value> {
3534
let cacheKey = options.cacheKeyFunction?(key) ?? key
3635

@@ -64,7 +63,21 @@ final public class DataLoader<Key: Hashable, Value> {
6463

6564
return future
6665
}
67-
66+
67+
/// Loads multiple keys, promising an array of values:
68+
///
69+
/// ```
70+
/// let aAndB = myLoader.loadMany(keys: [ "a", "b" ], on: eventLoopGroup).wait()
71+
/// ```
72+
///
73+
/// This is equivalent to the more verbose:
74+
///
75+
/// ```
76+
/// let aAndB = [
77+
/// myLoader.load(key: "a", on: eventLoopGroup),
78+
/// myLoader.load(key: "b", on: eventLoopGroup)
79+
/// ].flatten(on: eventLoopGroup).wait()
80+
/// ```
6881
public func loadMany(keys: [Key], on eventLoop: EventLoopGroup) throws -> EventLoopFuture<[Value]> {
6982
guard !keys.isEmpty else { return eventLoop.next().makeSucceededFuture([]) }
7083

@@ -86,18 +99,28 @@ final public class DataLoader<Key: Hashable, Value> {
8699

87100
return promise.futureResult
88101
}
89-
102+
103+
/// Clears the value at `key` from the cache, if it exists. Returns itself for
104+
/// method chaining.
105+
@discardableResult
90106
func clear(key: Key) -> DataLoader<Key, Value> {
91107
let cacheKey = options.cacheKeyFunction?(key) ?? key
92108
futureCache.removeValue(forKey: cacheKey)
93109
return self
94110
}
95-
111+
112+
/// Clears the entire cache. To be used when some event results in unknown
113+
/// invalidations across this particular `DataLoader`. Returns itself for
114+
/// method chaining.
115+
@discardableResult
96116
func clearAll() -> DataLoader<Key, Value> {
97117
futureCache.removeAll()
98118
return self
99119
}
100120

121+
/// Adds the provied key and value to the cache. If the key already exists, no
122+
/// change is made. Returns itself for method chaining.
123+
@discardableResult
101124
func prime(key: Key, value: Value, on eventLoop: EventLoopGroup) -> DataLoader<Key, Value> {
102125
let cacheKey = options.cacheKeyFunction?(key) ?? key
103126

@@ -111,7 +134,25 @@ final public class DataLoader<Key: Hashable, Value> {
111134
return self
112135
}
113136

114-
// MARK: - Private
137+
public func dispatchQueue(on eventLoop: EventLoopGroup) throws {
138+
// Take the current loader queue, replacing it with an empty queue.
139+
let queue = self.queue
140+
self.queue = []
141+
142+
// If a maxBatchSize was provided and the queue is longer, then segment the
143+
// queue into multiple batches, otherwise treat the queue as a single batch.
144+
if let maxBatchSize = options.maxBatchSize, maxBatchSize > 0 && maxBatchSize < queue.count {
145+
for i in 0...(queue.count / maxBatchSize) {
146+
let startIndex = i * maxBatchSize
147+
let endIndex = (i + 1) * maxBatchSize
148+
let slicedQueue = queue[startIndex..<min(endIndex, queue.count)]
149+
try dispatchQueueBatch(queue: Array(slicedQueue), on: eventLoop)
150+
}
151+
} else {
152+
try dispatchQueueBatch(queue: queue, on: eventLoop)
153+
}
154+
}
155+
115156
private func dispatchQueueBatch(queue: LoaderQueue<Key, Value>, on eventLoop: EventLoopGroup) throws {
116157
let keys = queue.map { $0.key }
117158

@@ -141,25 +182,6 @@ final public class DataLoader<Key: Hashable, Value> {
141182
}
142183
}
143184

144-
public func dispatchQueue(on eventLoop: EventLoopGroup) throws {
145-
// Take the current loader queue, replacing it with an empty queue.
146-
let queue = self.queue
147-
self.queue = []
148-
149-
// If a maxBatchSize was provided and the queue is longer, then segment the
150-
// queue into multiple batches, otherwise treat the queue as a single batch.
151-
if let maxBatchSize = options.maxBatchSize, maxBatchSize > 0 && maxBatchSize < queue.count {
152-
for i in 0...(queue.count / maxBatchSize) {
153-
let startIndex = i * maxBatchSize
154-
let endIndex = (i + 1) * maxBatchSize
155-
let slicedQueue = queue[startIndex..<min(endIndex, queue.count)]
156-
try dispatchQueueBatch(queue: Array(slicedQueue), on: eventLoop)
157-
}
158-
} else {
159-
try dispatchQueueBatch(queue: queue, on: eventLoop)
160-
}
161-
}
162-
163185
private func failedDispatch(queue: LoaderQueue<Key, Value>, error: Error) {
164186
queue.forEach { (key, promise) in
165187
_ = clear(key: key)

0 commit comments

Comments
 (0)