Skip to content

Commit

Permalink
Merge pull request #715 from apollographql/add/client-protocol
Browse files Browse the repository at this point in the history
Add ApolloClientProtocol
  • Loading branch information
designatednerd authored Aug 16, 2019
2 parents 426818a + aa64e2c commit 2c13ccf
Show file tree
Hide file tree
Showing 14 changed files with 586 additions and 263 deletions.
4 changes: 4 additions & 0 deletions Apollo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
54DDB0921EA045870009DD99 /* InMemoryNormalizedCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */; };
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */; };
9B708AAD2305884500604A11 /* ApolloClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */; };
9B95EDC022CAA0B000702BB2 /* GETTransformerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B95EDBF22CAA0AF00702BB2 /* GETTransformerTests.swift */; };
9BA1244A22D8A8EA00BF1D24 /* JSONSerialziation+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1244922D8A8EA00BF1D24 /* JSONSerialziation+Sorting.swift */; };
9BA1245E22DE116B00BF1D24 /* Result+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1245D22DE116B00BF1D24 /* Result+Helpers.swift */; };
Expand Down Expand Up @@ -262,6 +263,7 @@
90690D2322433C5900FC2E54 /* Apollo-Target-CacheDependentTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-CacheDependentTests.xcconfig"; sourceTree = "<group>"; };
90690D2422433C8000FC2E54 /* Apollo-Target-PerformanceTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-PerformanceTests.xcconfig"; sourceTree = "<group>"; };
90690D2522433CAF00FC2E54 /* Apollo-Target-TestSupport.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Target-TestSupport.xcconfig"; sourceTree = "<group>"; };
9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloClientProtocol.swift; sourceTree = "<group>"; };
9B8D864E22E7A846001F6D50 /* RepoURL.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = RepoURL.graphql; sourceTree = "<group>"; };
9B95EDBF22CAA0AF00702BB2 /* GETTransformerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GETTransformerTests.swift; sourceTree = "<group>"; };
9BA1244922D8A8EA00BF1D24 /* JSONSerialziation+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONSerialziation+Sorting.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -627,6 +629,7 @@
isa = PBXGroup;
children = (
9FC750621D2A59F600458D91 /* ApolloClient.swift */,
9B708AAC2305884500604A11 /* ApolloClientProtocol.swift */,
9FC9A9D21E2FD48B0023C4D5 /* GraphQLError.swift */,
9FC750601D2A59C300458D91 /* GraphQLOperation.swift */,
9FCDFD281E33D0CE007519DC /* GraphQLQueryWatcher.swift */,
Expand Down Expand Up @@ -1203,6 +1206,7 @@
9BDE43DD22C6705300FD7C7F /* GraphQLHTTPResponseError.swift in Sources */,
9FCDFD231E33A0D8007519DC /* AsynchronousOperation.swift in Sources */,
9BA1244A22D8A8EA00BF1D24 /* JSONSerialziation+Sorting.swift in Sources */,
9B708AAD2305884500604A11 /* ApolloClientProtocol.swift in Sources */,
C377CCA922D798BD00572E03 /* GraphQLFile.swift in Sources */,
9FC9A9CC1E2FD0760023C4D5 /* Record.swift in Sources */,
9FC4B9201D2A6F8D0046A641 /* JSON.swift in Sources */,
Expand Down
203 changes: 95 additions & 108 deletions Sources/Apollo/ApolloClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,13 @@ public enum CachePolicy {
/// - result: The result of a performed operation. Will have a `GraphQLResult` with any parsed data and any GraphQL errors on `success`, and an `Error` on `failure`.
public typealias GraphQLResultHandler<Data> = (Result<GraphQLResult<Data>, Error>) -> Void

/// The `ApolloClient` class provides the core API for Apollo. This API provides methods to fetch and watch queries, and to perform mutations.
/// The `ApolloClient` class implements the core API for Apollo by conforming to `ApolloClientProtocol`.
public class ApolloClient {

let networkTransport: NetworkTransport

public let store: ApolloStore

public var cacheKeyForObject: CacheKeyForObject? {
get {
return store.cacheKeyForObject
}

set {
store.cacheKeyForObject = newValue
}
}


public let store: ApolloStore // <- conformance to ApolloClientProtocol

private let queue: DispatchQueue
private let operationQueue: OperationQueue

Expand All @@ -55,7 +46,7 @@ public class ApolloClient {
///
/// - Parameters:
/// - networkTransport: A network transport used to send operations to a server.
/// - store: A store used as a local cache. Defaults to an empty store backed by an in memory cache.
/// - store: A store used as a local cache. Should default to an empty store backed by an in-memory cache.
public init(networkTransport: NetworkTransport, store: ApolloStore = ApolloStore(cache: InMemoryNormalizedCache())) {
self.networkTransport = networkTransport
self.store = store
Expand All @@ -71,99 +62,6 @@ public class ApolloClient {
public convenience init(url: URL) {
self.init(networkTransport: HTTPNetworkTransport(url: url))
}

/// Clears the underlying cache.
/// Be aware: In more complex setups, the same underlying cache can be used across multiple instances, so if you call this on one instance, it'll clear that cache across all instances which share that cache.
///
/// - Returns: Promise which fulfills when clear is complete.
public func clearCache() -> Promise<Void> {
return store.clearCache()
}

/// Fetches a query from the server or from the local cache, depending on the current contents of the cache and the specified cache policy.
///
/// - Parameters:
/// - query: The query to fetch.
/// - cachePolicy: A cache policy that specifies when results should be fetched from the server and when data should be loaded from the local cache.
/// - queue: A dispatch queue on which the result handler will be called. Defaults to the main queue.
/// - resultHandler: An optional closure that is called when query results are available or when an error occurs.
/// - Returns: An object that can be used to cancel an in progress fetch.
@discardableResult public func fetch<Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy = .returnCacheDataElseFetch, context: UnsafeMutableRawPointer? = nil, queue: DispatchQueue = DispatchQueue.main, resultHandler: GraphQLResultHandler<Query.Data>? = nil) -> Cancellable {
let resultHandler = wrapResultHandler(resultHandler, queue: queue)

// If we don't have to go through the cache, there is no need to create an operation
// and we can return a network task directly
if cachePolicy == .fetchIgnoringCacheData || cachePolicy == .fetchIgnoringCacheCompletely {
return send(operation: query, shouldPublishResultToStore: cachePolicy != .fetchIgnoringCacheCompletely, context: context, resultHandler: resultHandler)
} else {
let operation = FetchQueryOperation(client: self, query: query, cachePolicy: cachePolicy, context: context, resultHandler: resultHandler)
operationQueue.addOperation(operation)
return operation
}
}

/// Watches a query by first fetching an initial result from the server or from the local cache, depending on the current contents of the cache and the specified cache policy. After the initial fetch, the returned query watcher object will get notified whenever any of the data the query result depends on changes in the local cache, and calls the result handler again with the new result.
///
/// - Parameters:
/// - query: The query to fetch.
/// - fetchHTTPMethod: The HTTP Method to be used.
/// - cachePolicy: A cache policy that specifies when results should be fetched from the server or from the local cache.
/// - queue: A dispatch queue on which the result handler will be called. Defaults to the main queue.
/// - resultHandler: An optional closure that is called when query results are available or when an error occurs.
/// - Returns: A query watcher object that can be used to control the watching behavior.
public func watch<Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy = .returnCacheDataElseFetch, queue: DispatchQueue = DispatchQueue.main, resultHandler: @escaping GraphQLResultHandler<Query.Data>) -> GraphQLQueryWatcher<Query> {
let watcher = GraphQLQueryWatcher(client: self, query: query, resultHandler: wrapResultHandler(resultHandler, queue: queue))
watcher.fetch(cachePolicy: cachePolicy)
return watcher
}

/// Performs a mutation by sending it to the server.
///
/// - Parameters:
/// - mutation: The mutation to perform.
/// - fetchHTTPMethod: The HTTP Method to be used.
/// - queue: A dispatch queue on which the result handler will be called. Defaults to the main queue.
/// - resultHandler: An optional closure that is called when mutation results are available or when an error occurs.
/// - Returns: An object that can be used to cancel an in progress mutation.
@discardableResult public func perform<Mutation: GraphQLMutation>(mutation: Mutation, context: UnsafeMutableRawPointer? = nil, queue: DispatchQueue = DispatchQueue.main, resultHandler: GraphQLResultHandler<Mutation.Data>? = nil) -> Cancellable {
return send(operation: mutation, shouldPublishResultToStore: true, context: context, resultHandler: wrapResultHandler(resultHandler, queue: queue))
}

/// Uploads the given files with the given operation.
///
/// - Parameters:
/// - operation: The operation to send
/// - files: An array of `GraphQLFile` objects to send.
/// - queue: A dispatch queue on which the result handler will be called. Defaults to the main queue.
/// - completionHandler: The completion handler to execute when the request completes or errors
/// - Returns: An object that can be used to cancel an in progress request.
/// - Throws: If your `networkTransport` does nto also conform to `UploadingNetworkTransport`.
@discardableResult public func upload<Operation: GraphQLOperation>(operation: Operation, context: UnsafeMutableRawPointer? = nil, files: [GraphQLFile], queue: DispatchQueue = .main, resultHandler: GraphQLResultHandler<Operation.Data>? = nil) -> Cancellable {
let wrappedHandler = wrapResultHandler(resultHandler, queue: queue)
guard let uploadingTransport = self.networkTransport as? UploadingNetworkTransport else {
assertionFailure("Trying to upload without an uploading transport. Please make sure your network transport conforms to `UploadingNetworkTransport`.")
wrappedHandler(.failure(ApolloClientError.noUploadTransport))
return EmptyCancellable()
}

return uploadingTransport.upload(operation: operation, files: files) { result in
self.handleOperationResult(shouldPublishResultToStore: true,
context: context, result,
resultHandler: wrappedHandler)
}
}

/// Subscribe to a subscription
///
/// - Parameters:
/// - subscription: The subscription to subscribe to.
/// - fetchHTTPMethod: The HTTP Method to be used.
/// - queue: A dispatch queue on which the result handler will be called. Defaults to the main queue.
/// - resultHandler: An optional closure that is called when mutation results are available or when an error occurs.
/// - Returns: An object that can be used to cancel an in progress subscription.
@discardableResult public func subscribe<Subscription: GraphQLSubscription>(subscription: Subscription, queue: DispatchQueue = DispatchQueue.main, resultHandler: @escaping GraphQLResultHandler<Subscription.Data>) -> Cancellable {
return send(operation: subscription, shouldPublishResultToStore: true, context: nil, resultHandler: wrapResultHandler(resultHandler, queue: queue))
}

fileprivate func send<Operation: GraphQLOperation>(operation: Operation, shouldPublishResultToStore: Bool, context: UnsafeMutableRawPointer?, resultHandler: @escaping GraphQLResultHandler<Operation.Data>) -> Cancellable {
return networkTransport.send(operation: operation) { result in
Expand Down Expand Up @@ -206,6 +104,95 @@ public class ApolloClient {
}
}

// MARK: - ApolloClientProtocol conformance

extension ApolloClient: ApolloClientProtocol {

public var cacheKeyForObject: CacheKeyForObject? {
get {
return self.store.cacheKeyForObject
}

set {
self.store.cacheKeyForObject = newValue
}
}

public func clearCache() -> Promise<Void> {
return self.store.clearCache()
}

@discardableResult public func fetch<Query: GraphQLQuery>(query: Query,
cachePolicy: CachePolicy = .returnCacheDataElseFetch,
context: UnsafeMutableRawPointer? = nil,
queue: DispatchQueue = DispatchQueue.main,
resultHandler: GraphQLResultHandler<Query.Data>? = nil) -> Cancellable {
let resultHandler = wrapResultHandler(resultHandler, queue: queue)

// If we don't have to go through the cache, there is no need to create an operation
// and we can return a network task directly
if cachePolicy == .fetchIgnoringCacheData || cachePolicy == .fetchIgnoringCacheCompletely {
return self.send(operation: query, shouldPublishResultToStore: cachePolicy != .fetchIgnoringCacheCompletely, context: context, resultHandler: resultHandler)
} else {
let operation = FetchQueryOperation(client: self, query: query, cachePolicy: cachePolicy, context: context, resultHandler: resultHandler)
self.operationQueue.addOperation(operation)
return operation
}
}

public func watch<Query: GraphQLQuery>(query: Query,
cachePolicy: CachePolicy = .returnCacheDataElseFetch,
queue: DispatchQueue = .main,
resultHandler: @escaping GraphQLResultHandler<Query.Data>) -> GraphQLQueryWatcher<Query> {
let watcher = GraphQLQueryWatcher(client: self,
query: query,
resultHandler: wrapResultHandler(resultHandler, queue: queue))
watcher.fetch(cachePolicy: cachePolicy)
return watcher
}

@discardableResult
public func perform<Mutation: GraphQLMutation>(mutation: Mutation,
context: UnsafeMutableRawPointer? = nil,
queue: DispatchQueue = DispatchQueue.main,
resultHandler: GraphQLResultHandler<Mutation.Data>? = nil) -> Cancellable {
return self.send(operation: mutation,
shouldPublishResultToStore: true,
context: context,
resultHandler: wrapResultHandler(resultHandler, queue: queue))
}

@discardableResult
public func upload<Operation: GraphQLOperation>(operation: Operation,
context: UnsafeMutableRawPointer? = nil,
files: [GraphQLFile],
queue: DispatchQueue = .main,
resultHandler: GraphQLResultHandler<Operation.Data>? = nil) -> Cancellable {
let wrappedHandler = wrapResultHandler(resultHandler, queue: queue)
guard let uploadingTransport = self.networkTransport as? UploadingNetworkTransport else {
assertionFailure("Trying to upload without an uploading transport. Please make sure your network transport conforms to `UploadingNetworkTransport`.")
wrappedHandler(.failure(ApolloClientError.noUploadTransport))
return EmptyCancellable()
}

return uploadingTransport.upload(operation: operation, files: files) { result in
self.handleOperationResult(shouldPublishResultToStore: true,
context: context, result,
resultHandler: wrappedHandler)
}
}

@discardableResult
public func subscribe<Subscription: GraphQLSubscription>(subscription: Subscription,
queue: DispatchQueue = .main,
resultHandler: @escaping GraphQLResultHandler<Subscription.Data>) -> Cancellable {
return self.send(operation: subscription,
shouldPublishResultToStore: true,
context: nil,
resultHandler: wrapResultHandler(resultHandler, queue: queue))
}
}

private func wrapResultHandler<Data>(_ resultHandler: GraphQLResultHandler<Data>?, queue handlerQueue: DispatchQueue) -> GraphQLResultHandler<Data> {
guard let resultHandler = resultHandler else {
return { _ in }
Expand Down
Loading

0 comments on commit 2c13ccf

Please sign in to comment.