Skip to content

Commit

Permalink
Add cachePolicy to ApolloClient#fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnwalraven committed Jan 21, 2017
1 parent cbc245b commit 76caa60
Show file tree
Hide file tree
Showing 4 changed files with 400 additions and 24 deletions.
22 changes: 21 additions & 1 deletion Apollo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
9FC9A9D11E2FD3FE0023C4D5 /* Fragments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC9A9CF1E2FD3FE0023C4D5 /* Fragments.swift */; };
9FC9A9D31E2FD48B0023C4D5 /* GraphQLError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC9A9D21E2FD48B0023C4D5 /* GraphQLError.swift */; };
9FC9A9D41E2FD48B0023C4D5 /* GraphQLError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC9A9D21E2FD48B0023C4D5 /* GraphQLError.swift */; };
9FCDFD1F1E32EABB007519DC /* FetchQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FCDFD1E1E32EABB007519DC /* FetchQueryTests.swift */; };
9FCDFD201E32EABB007519DC /* FetchQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FCDFD1E1E32EABB007519DC /* FetchQueryTests.swift */; };
9FCDFD231E33A0D8007519DC /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */; };
9FCDFD241E33A0D8007519DC /* AsynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */; };
9FDC98481E2A369C005815DF /* HeroAndFriendsNames.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 9FDC98471E2A369C005815DF /* HeroAndFriendsNames.graphql */; };
9FDC98491E2A369C005815DF /* HeroAndFriendsNames.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 9FDC98471E2A369C005815DF /* HeroAndFriendsNames.graphql */; };
9FE3F3981DADBD870072078F /* check-and-run-apollo-codegen.sh in Resources */ = {isa = PBXBuildFile; fileRef = 9FE3F3971DADBD870072078F /* check-and-run-apollo-codegen.sh */; };
Expand Down Expand Up @@ -174,6 +178,8 @@
9FC9A9CB1E2FD0760023C4D5 /* Record.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Record.swift; sourceTree = "<group>"; };
9FC9A9CF1E2FD3FE0023C4D5 /* Fragments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fragments.swift; sourceTree = "<group>"; };
9FC9A9D21E2FD48B0023C4D5 /* GraphQLError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLError.swift; sourceTree = "<group>"; };
9FCDFD1E1E32EABB007519DC /* FetchQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchQueryTests.swift; sourceTree = "<group>"; };
9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsynchronousOperation.swift; sourceTree = "<group>"; };
9FDC98471E2A369C005815DF /* HeroAndFriendsNames.graphql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = HeroAndFriendsNames.graphql; sourceTree = "<group>"; };
9FE3F3971DADBD870072078F /* check-and-run-apollo-codegen.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "check-and-run-apollo-codegen.sh"; path = "scripts/check-and-run-apollo-codegen.sh"; sourceTree = SOURCE_ROOT; };
9FEB050C1DB5732300DA3B44 /* JSONSerializationFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONSerializationFormat.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -326,7 +332,7 @@
9FC9A9CE1E2FD0CC0023C4D5 /* Network */,
9F27D4601D40363A00715680 /* Execution */,
9FC4B9231D2BE4F00046A641 /* JSON */,
9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */,
9FCDFD211E33A09F007519DC /* Utilities */,
9FE3F3961DADBD0D0072078F /* Supporting Files */,
);
name = Apollo;
Expand All @@ -336,6 +342,7 @@
9FC750521D2A532D00458D91 /* ApolloTests */ = {
isa = PBXGroup;
children = (
9FCDFD1E1E32EABB007519DC /* FetchQueryTests.swift */,
9FF90A6C1DDDEB420034C3B6 /* ParseQueryResponseTests.swift */,
9F295E301E27534800A24949 /* NormalizeQueryResults.swift */,
9F55347D1DE1DB3500E54264 /* LoadQueryFromStoreTests.swift */,
Expand Down Expand Up @@ -379,6 +386,15 @@
name = Integration;
sourceTree = "<group>";
};
9FCDFD211E33A09F007519DC /* Utilities */ = {
isa = PBXGroup;
children = (
9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */,
9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */,
);
name = Utilities;
sourceTree = "<group>";
};
9FE3F3961DADBD0D0072078F /* Supporting Files */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -777,6 +793,7 @@
9FC9A9CD1E2FD0760023C4D5 /* Record.swift in Sources */,
3C4DBD1A1DD231E1005B61D0 /* Utilities.swift in Sources */,
3C4DBD161DD231D9005B61D0 /* NetworkTransport.swift in Sources */,
9FCDFD241E33A0D8007519DC /* AsynchronousOperation.swift in Sources */,
9FC9A9C01E2C27FB0023C4D5 /* GraphQLResult.swift in Sources */,
3C4DBD231DD231F2005B61D0 /* JSON.swift in Sources */,
9FC9A9C31E2D3CAF0023C4D5 /* GraphQLTypes.swift in Sources */,
Expand All @@ -801,6 +818,7 @@
9FF90A701DDDEB420034C3B6 /* GraphQLInputValueEncodingTests.swift in Sources */,
9FF90A771DDDEB4C0034C3B6 /* Utilities.swift in Sources */,
9F55347F1DE1DB3500E54264 /* LoadQueryFromStoreTests.swift in Sources */,
9FCDFD201E32EABB007519DC /* FetchQueryTests.swift in Sources */,
9FF90A721DDDEB420034C3B6 /* GraphQLResultReaderTests.swift in Sources */,
9F295E331E27534800A24949 /* NormalizeQueryResults.swift in Sources */,
9F4FB2751DD853D800529F53 /* API.swift in Sources */,
Expand All @@ -819,6 +837,7 @@
9FC9A9D31E2FD48B0023C4D5 /* GraphQLError.swift in Sources */,
9FEB050D1DB5732300DA3B44 /* JSONSerializationFormat.swift in Sources */,
9FC9A9C51E2D6CE70023C4D5 /* Field.swift in Sources */,
9FCDFD231E33A0D8007519DC /* AsynchronousOperation.swift in Sources */,
9FC9A9CC1E2FD0760023C4D5 /* Record.swift in Sources */,
9FC4B9201D2A6F8D0046A641 /* JSON.swift in Sources */,
9F578D901D8D2CB300C0EA36 /* Utilities.swift in Sources */,
Expand All @@ -843,6 +862,7 @@
9FF90A6F1DDDEB420034C3B6 /* GraphQLInputValueEncodingTests.swift in Sources */,
9FF90A761DDDEB4C0034C3B6 /* Utilities.swift in Sources */,
9F55347E1DE1DB3500E54264 /* LoadQueryFromStoreTests.swift in Sources */,
9FCDFD1F1E32EABB007519DC /* FetchQueryTests.swift in Sources */,
9FF90A711DDDEB420034C3B6 /* GraphQLResultReaderTests.swift in Sources */,
9F295E311E27534800A24949 /* NormalizeQueryResults.swift in Sources */,
9F20CBC21DAF03F900A139BB /* API.swift in Sources */,
Expand Down
131 changes: 108 additions & 23 deletions Sources/ApolloClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,139 @@ public protocol Cancellable {
func cancel()
}

public enum CachePolicy {
case returnCacheDataElseFetch
case fetchIgnoringCacheData
case returnCacheDataDontFetch
}

public typealias OperationCompletionHandler<Operation: GraphQLOperation> = (GraphQLResult<Operation.Data>?, Error?) -> Void

public class ApolloClient {
private let operationQueue: OperationQueue

let networkTransport: NetworkTransport
let store: ApolloStore

var cacheKeyForObject: CacheKeyForObject?
public var cacheKeyForObject: CacheKeyForObject?

public init(networkTransport: NetworkTransport) {
public init(networkTransport: NetworkTransport, store: ApolloStore = ApolloStore()) {
operationQueue = OperationQueue()
self.networkTransport = networkTransport
self.store = ApolloStore()
self.store = store
}

public convenience init(url: URL) {
self.init(networkTransport: HTTPNetworkTransport(url: url))
}

@discardableResult public func fetch<Query: GraphQLQuery>(query: Query, queue: DispatchQueue = DispatchQueue.main, completionHandler: @escaping (GraphQLResult<Query.Data>?, Error?) -> Void) -> Cancellable {
return perform(operation: query, completionHandler: completionHandler)
@discardableResult public func fetch<Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy = .returnCacheDataElseFetch, queue: DispatchQueue = DispatchQueue.main, completionHandler: @escaping OperationCompletionHandler<Query>) -> Cancellable {
if cachePolicy == .fetchIgnoringCacheData {
return send(operation: query) { (result, error) in
queue.async {
completionHandler(result, error)
}
}
} else {
let operation = FetchQueryOperation(client: self, query: query, cachePolicy: cachePolicy) { (result, error) in
queue.async {
completionHandler(result, error)
}
}

operationQueue.addOperation(operation)
return operation
}
}

@discardableResult public func perform<Mutation: GraphQLMutation>(mutation: Mutation, queue: DispatchQueue = DispatchQueue.main, completionHandler: @escaping (GraphQLResult<Mutation.Data>?, Error?) -> Void) -> Cancellable {
return perform(operation: mutation, completionHandler: completionHandler)
public func watch<Query: GraphQLQuery>(query: Query, queue: DispatchQueue = DispatchQueue.main, handler: @escaping OperationCompletionHandler<Query>) -> Cancellable {
return fetch(query: query, queue: queue, completionHandler: handler)
}

@discardableResult public func perform<Mutation: GraphQLMutation>(mutation: Mutation, queue: DispatchQueue = DispatchQueue.main, completionHandler: @escaping OperationCompletionHandler<Mutation>) -> Cancellable {
return send(operation: mutation) { (result, error) in
queue.async {
completionHandler(result, error)
}
}
}

private func perform<Operation: GraphQLOperation>(operation: Operation, queue: DispatchQueue = DispatchQueue.main, completionHandler: @escaping (GraphQLResult<Operation.Data>?, Error?) -> Void) -> Cancellable {
fileprivate func send<Operation: GraphQLOperation>(operation: Operation, completionHandler: @escaping OperationCompletionHandler<Operation>) -> Cancellable {
return networkTransport.send(operation: operation) { (response, error) in
guard let response = response else {
queue.async {
completionHandler(nil, error)
}
completionHandler(nil, error)
return
}

do {
let normalizer = GraphQLResultNormalizer()
normalizer.cacheKeyForObject = self.cacheKeyForObject

let result = try response.parseResult(delegate: normalizer)

queue.async {
DispatchQueue.global(qos: .default).async {
do {
let normalizer = GraphQLResultNormalizer()
normalizer.cacheKeyForObject = self.cacheKeyForObject

let result = try response.parseResult(delegate: normalizer)

self.store.publish(changedRecords: normalizer.records)

completionHandler(result, nil)
}

self.store.publish(changedRecords: normalizer.records)
} catch {
queue.async {
} catch {
completionHandler(nil, error)
}
}
}
}
}

private final class FetchQueryOperation<Query: GraphQLQuery>: AsynchronousOperation, Cancellable {
unowned let client: ApolloClient
let query: Query
let cachePolicy: CachePolicy
let completionHandler: OperationCompletionHandler<Query>

private var networkTask: Cancellable?

init(client: ApolloClient, query: Query, cachePolicy: CachePolicy, completionHandler: @escaping OperationCompletionHandler<Query>) {
self.client = client
self.query = query
self.cachePolicy = cachePolicy
self.completionHandler = completionHandler
}

override public func start() {
if isCancelled {
state = .finished
return
}

state = .executing

if cachePolicy != .fetchIgnoringCacheData {
if let data = try? client.store.load(query: query) {
completionHandler(GraphQLResult(data: data, errors: nil), nil)
state = .finished
return
}
}

if isCancelled {
state = .finished
return
}

if cachePolicy == .returnCacheDataDontFetch {
completionHandler(nil, nil)
state = .finished
return
}

networkTask = client.send(operation: query) { (result, error) in
self.completionHandler(result, error)
self.state = .finished
return
}
}

override public func cancel() {
super.cancel()
networkTask?.cancel()
}
}
48 changes: 48 additions & 0 deletions Sources/AsynchronousOperation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

class AsynchronousOperation: Operation {
class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
return ["state"]
}

class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
return ["state"]
}

enum State {
case initialized
case ready
case executing
case finished
}

var state: State = .initialized {
willSet {
willChangeValue(forKey: "state")
}
didSet {
print(state)
didChangeValue(forKey: "state")
}
}

override var isAsynchronous: Bool {
return true
}

override var isReady: Bool {
let ready = super.isReady
if ready {
state = .ready
}
return ready
}

override var isExecuting: Bool {
return state == .executing
}

override var isFinished: Bool {
return state == .finished
}
}
Loading

0 comments on commit 76caa60

Please sign in to comment.