-
Notifications
You must be signed in to change notification settings - Fork 749
Expand file tree
/
Copy pathGraphQLQueryWatcher.swift
More file actions
93 lines (76 loc) · 3.18 KB
/
GraphQLQueryWatcher.swift
File metadata and controls
93 lines (76 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import Dispatch
/// A `GraphQLQueryWatcher` is responsible for watching the store, and calling the result handler with a new result whenever any of the data the previous result depends on changes.
///
/// NOTE: The store retains the watcher while subscribed. You must call `cancel()` on your query watcher when you no longer need results. Failure to call `cancel()` before releasing your reference to the returned watcher will result in a memory leak.
public final class GraphQLQueryWatcher<Query: GraphQLQuery>: Cancellable, ApolloStoreSubscriber {
weak var client: ApolloClientProtocol?
public let query: Query
let resultHandler: GraphQLResultHandler<Query.Data>
private var context = 0
private weak var fetching: Cancellable?
private var dependentKeys: Set<CacheKey>?
/// Designated initializer
///
/// - Parameters:
/// - client: The client protocol to pass in
/// - query: The query to watch
/// - resultHandler: The result handler to call with changes.
public init(client: ApolloClientProtocol,
query: Query,
resultHandler: @escaping GraphQLResultHandler<Query.Data>) {
self.client = client
self.query = query
self.resultHandler = resultHandler
client.store.subscribe(self)
}
/// Refetch a query from the server.
public func refetch() {
fetch(cachePolicy: .fetchIgnoringCacheData)
}
// Watchers always call result handlers on the main queue.
private let callbackQueue: DispatchQueue = .main
func fetch(cachePolicy: CachePolicy) {
// Cancel anything already in flight before starting a new fetch
fetching?.cancel()
fetching = client?.fetch(query: query, cachePolicy: cachePolicy, context: &context, queue: callbackQueue) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let graphQLResult):
self.dependentKeys = graphQLResult.dependentKeys
case .failure:
break
}
self.resultHandler(result)
}
}
/// Cancel any in progress fetching operations and unsubscribe from the store.
public func cancel() {
fetching?.cancel()
client?.store.unsubscribe(self)
}
func store(_ store: ApolloStore,
didChangeKeys changedKeys: Set<CacheKey>,
context: UnsafeMutableRawPointer?) {
if context == &self.context { return }
guard let dependentKeys = dependentKeys else { return }
if !dependentKeys.isDisjoint(with: changedKeys) {
// First, attempt to reload the query from the cache directly, in order not to interrupt any in-flight server-side fetch.
store.load(query: query) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let graphQLResult):
self.callbackQueue.async { [weak self] in
guard let self = self else {
return
}
self.dependentKeys = graphQLResult.dependentKeys
self.resultHandler(result)
}
case .failure:
// If the cache fetch is not successful, for instance if the data is missing, refresh from the server.
self.fetch(cachePolicy: .fetchIgnoringCacheData)
}
}
}
}
}