Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a protocol for public methods of Apollo client #693

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Apollo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
01B90C0D22F8D513008ED99F /* ApolloClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B90C0C22F8D513008ED99F /* ApolloClientProtocol.swift */; };
54DDB0921EA045870009DD99 /* InMemoryNormalizedCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */; };
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */; };
9B95EDC022CAA0B000702BB2 /* GETTransformerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B95EDBF22CAA0AF00702BB2 /* GETTransformerTests.swift */; };
Expand Down Expand Up @@ -249,6 +250,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
01B90C0C22F8D513008ED99F /* ApolloClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloClientProtocol.swift; sourceTree = "<group>"; };
54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryNormalizedCache.swift; sourceTree = "<group>"; };
5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPMethod.swift; sourceTree = "<group>"; };
90690D05224333DA00FC2E54 /* Apollo-Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Project-Debug.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -627,6 +629,7 @@
isa = PBXGroup;
children = (
9FC750621D2A59F600458D91 /* ApolloClient.swift */,
01B90C0C22F8D513008ED99F /* 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 */,
01B90C0D22F8D513008ED99F /* ApolloClientProtocol.swift in Sources */,
C377CCA922D798BD00572E03 /* GraphQLFile.swift in Sources */,
9FC9A9CC1E2FD0760023C4D5 /* Record.swift in Sources */,
9FC4B9201D2A6F8D0046A641 /* JSON.swift in Sources */,
Expand Down
41 changes: 2 additions & 39 deletions Sources/Apollo/ApolloClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public enum CachePolicy {
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.
public class ApolloClient {
public class ApolloClient: ApolloClientProtocol {
let networkTransport: NetworkTransport

public let store: ApolloStore
Expand Down Expand Up @@ -61,22 +61,10 @@ public class ApolloClient {
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)

Expand All @@ -91,41 +79,16 @@ public class ApolloClient {
}
}

/// 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))
}

/// 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))
}
Expand Down
82 changes: 82 additions & 0 deletions Sources/Apollo/ApolloClientProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Created by guillaume_sabran on 8/5/19.
// Copyright © 2019 Airbnb Inc. All rights reserved.

import Dispatch

// MARK: - Protocol

/// Describes the Apollo client interface
public protocol ApolloClientProtocol: class {

var store: ApolloStore { get }

var cacheKeyForObject: CacheKeyForObject? { get set }

/// 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.
func clearCache() -> Promise<Void>

/// 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 func fetch<Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy, context: UnsafeMutableRawPointer?, queue: DispatchQueue, resultHandler: GraphQLResultHandler<Query.Data>?) -> Cancellable

/// 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.
func watch<Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy, queue: DispatchQueue, resultHandler: @escaping GraphQLResultHandler<Query.Data>) -> GraphQLQueryWatcher<Query>

/// 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 func perform<Mutation: GraphQLMutation>(mutation: Mutation, context: UnsafeMutableRawPointer?, queue: DispatchQueue, resultHandler: GraphQLResultHandler<Mutation.Data>?) -> Cancellable

/// 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 func subscribe<Subscription: GraphQLSubscription>(subscription: Subscription, queue: DispatchQueue, resultHandler: @escaping GraphQLResultHandler<Subscription.Data>) -> Cancellable
}

// MARK: - Extension

public extension ApolloClientProtocol {

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to watch out for with these default implementations is that they will override the local implementation anywhere that the client is passed as ApolloClientProtocol. This is an extremely oversimplified example of that behavior.

I would recommend removing the default implementations here and just setting the default params on the actual implementations, since it seems like the default implementations are just there to re-add the default parameters.

}

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

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

@discardableResult func subscribe<Subscription: GraphQLSubscription>(subscription: Subscription, queue: DispatchQueue = DispatchQueue.main, resultHandler: @escaping GraphQLResultHandler<Subscription.Data>) -> Cancellable {
return subscribe(subscription: subscription, queue: queue, resultHandler: resultHandler)
}
}
4 changes: 2 additions & 2 deletions Sources/Apollo/GraphQLQueryWatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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.
public final class GraphQLQueryWatcher<Query: GraphQLQuery>: Cancellable, ApolloStoreSubscriber {
weak var client: ApolloClient?
weak var client: ApolloClientProtocol?
let query: Query
let resultHandler: GraphQLResultHandler<Query.Data>

Expand All @@ -12,7 +12,7 @@ public final class GraphQLQueryWatcher<Query: GraphQLQuery>: Cancellable, Apollo

private var dependentKeys: Set<CacheKey>?

init(client: ApolloClient, query: Query, resultHandler: @escaping GraphQLResultHandler<Query.Data>) {
init(client: ApolloClientProtocol, query: Query, resultHandler: @escaping GraphQLResultHandler<Query.Data>) {
self.client = client
self.query = query
self.resultHandler = resultHandler
Expand Down