Skip to content

Commit

Permalink
Merge pull request #709 from apollographql/rm/public-promise
Browse files Browse the repository at this point in the history
Remove Public Promise
  • Loading branch information
designatednerd authored Sep 16, 2019
2 parents bd6f03e + 8f24739 commit 4693ad8
Show file tree
Hide file tree
Showing 16 changed files with 525 additions and 265 deletions.
4 changes: 4 additions & 0 deletions Apollo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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 */; };
9BA3130E2302BEA5007B7FC5 /* DispatchQueue+Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA3130D2302BEA5007B7FC5 /* DispatchQueue+Optional.swift */; };
9BDE43D122C6655300FD7C7F /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BDE43D022C6655200FD7C7F /* Cancellable.swift */; };
9BDE43DD22C6705300FD7C7F /* GraphQLHTTPResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BDE43DC22C6705300FD7C7F /* GraphQLHTTPResponseError.swift */; };
9BDE43DF22C6708600FD7C7F /* GraphQLHTTPRequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BDE43DE22C6708600FD7C7F /* GraphQLHTTPRequestError.swift */; };
Expand Down Expand Up @@ -270,6 +271,7 @@
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>"; };
9BA1245D22DE116B00BF1D24 /* Result+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Helpers.swift"; sourceTree = "<group>"; };
9BA3130D2302BEA5007B7FC5 /* DispatchQueue+Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Optional.swift"; sourceTree = "<group>"; };
9BDE43D022C6655200FD7C7F /* Cancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cancellable.swift; sourceTree = "<group>"; };
9BDE43DC22C6705300FD7C7F /* GraphQLHTTPResponseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPResponseError.swift; sourceTree = "<group>"; };
9BDE43DE22C6708600FD7C7F /* GraphQLHTTPRequestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPRequestError.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -711,6 +713,7 @@
children = (
9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */,
9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */,
9BA3130D2302BEA5007B7FC5 /* DispatchQueue+Optional.swift */,
9FE941CF1E62C771007CDD89 /* Promise.swift */,
9BA1245D22DE116B00BF1D24 /* Result+Helpers.swift */,
9F19D8431EED568200C57247 /* ResultOrPromise.swift */,
Expand Down Expand Up @@ -1230,6 +1233,7 @@
9FE941D01E62C771007CDD89 /* Promise.swift in Sources */,
9BA1245E22DE116B00BF1D24 /* Result+Helpers.swift in Sources */,
9FC750631D2A59F600458D91 /* ApolloClient.swift in Sources */,
9BA3130E2302BEA5007B7FC5 /* DispatchQueue+Optional.swift in Sources */,
9F86B6901E65533D00B885FF /* GraphQLResponseGenerator.swift in Sources */,
9FC9A9C21E2D3CAF0023C4D5 /* GraphQLInputValue.swift in Sources */,
);
Expand Down
4 changes: 2 additions & 2 deletions Sources/Apollo/ApolloClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ extension ApolloClient: ApolloClientProtocol {
}
}

public func clearCache() -> Promise<Void> {
return self.store.clearCache()
public func clearCache(callbackQueue: DispatchQueue = .main, completion: ((Result<Void, Error>) -> Void)? = nil) {
self.store.clearCache(completion: completion)
}

@discardableResult public func fetch<Query: GraphQLQuery>(query: Query,
Expand Down
6 changes: 4 additions & 2 deletions Sources/Apollo/ApolloClientProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ public protocol ApolloClientProtocol: class {
/// 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>
/// - Parameters:
/// - callbackQueue: The queue to fall back on. Should default to the main queue.
/// - completion: [optional] A completion closure to execute when clearing has completed. Should default to nil.
func clearCache(callbackQueue: DispatchQueue, completion: ((Result<Void, Error>) -> 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.
///
Expand Down
135 changes: 110 additions & 25 deletions Sources/Apollo/ApolloStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ public final class ApolloStore {
/// Clears the instance of the cache. Note that a cache can be shared across multiple `ApolloClient` objects, so clearing that underlying cache will clear it for all clients.
///
/// - Returns: A promise which fulfills when the Cache is cleared.
public func clearCache() -> Promise<Void> {
return Promise<Void> { fulfill, reject in
queue.async(flags: .barrier) {
self.cacheLock.withWriteLock {
self.cache.clear()
public func clearCache(callbackQueue: DispatchQueue = .main, completion: ((Result<Void, Error>) -> Void)? = nil) {
queue.async(flags: .barrier) {
self.cacheLock.withWriteLock {
self.cache.clearPromise()
}.andThen {
fulfill(())
}
DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .success(()))
}
}
}
Expand All @@ -66,7 +66,7 @@ public final class ApolloStore {
return Promise<Void> { fulfill, reject in
queue.async(flags: .barrier) {
self.cacheLock.withWriteLock {
self.cache.merge(records: records)
self.cache.mergePromise(records: records)
}.andThen { changedKeys in
self.didChangeKeys(changedKeys, context: context)
fulfill(())
Expand All @@ -87,7 +87,7 @@ public final class ApolloStore {
}
}

public func withinReadTransaction<T>(_ body: @escaping (ReadTransaction) throws -> Promise<T>) -> Promise<T> {
func withinReadTransactionPromise<T>(_ body: @escaping (ReadTransaction) throws -> Promise<T>) -> Promise<T> {
return Promise<ReadTransaction> { fulfill, reject in
self.queue.async {
self.cacheLock.lockForReading()
Expand All @@ -99,14 +99,32 @@ public final class ApolloStore {
self.cacheLock.unlock()
}
}

public func withinReadTransaction<T>(_ body: @escaping (ReadTransaction) throws -> T) -> Promise<T> {
return withinReadTransaction {
Promise(fulfilled: try body($0))

/// Performs an operation within a read transaction
///
/// - Parameters:
/// - body: The body of the operation to perform.
/// - callbackQueue: [optional] The callback queue to use to perform the completion block on. Will perform on the current queue if not provided. Defaults to nil.
/// - completion: [optional] The completion block to perform when the read transaction completes. Defaults to nil.
public func withinReadTransaction<T>(_ body: @escaping (ReadTransaction) throws -> T,
callbackQueue: DispatchQueue? = nil,
completion: ((Result<T, Error>) -> Void)? = nil) {
_ = self.withinReadTransactionPromise {
Promise(fulfilled: try body($0))
}
.andThen { object in
DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .success(object))
}
.catch { error in
DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .failure(error))
}
}

public func withinReadWriteTransaction<T>(_ body: @escaping (ReadWriteTransaction) throws -> Promise<T>) -> Promise<T> {
func withinReadWriteTransactionPromise<T>(_ body: @escaping (ReadWriteTransaction) throws -> Promise<T>) -> Promise<T> {
return Promise<ReadWriteTransaction> { fulfill, reject in
self.queue.async(flags: .barrier) {
self.cacheLock.lockForWriting()
Expand All @@ -117,15 +135,33 @@ public final class ApolloStore {
self.cacheLock.unlock()
}
}

public func withinReadWriteTransaction<T>(_ body: @escaping (ReadWriteTransaction) throws -> T) -> Promise<T> {
return withinReadWriteTransaction {
Promise(fulfilled: try body($0))
}

/// Performs an operation within a read-write transaction
///
/// - Parameters:
/// - body: The body of the operation to perform
/// - callbackQueue: [optional] a callback queue to perform the action on. Will perform on the current queue if not provided. Defaults to nil.
/// - completion: [optional] a completion block to fire when the read-write transaction completes. Defaults to nil.
public func withinReadWriteTransaction<T>(_ body: @escaping (ReadWriteTransaction) throws -> T,
callbackQueue: DispatchQueue? = nil,
completion: ((Result<T, Error>) -> Void)? = nil) {
_ = self.withinReadWriteTransactionPromise {
Promise(fulfilled: try body($0))
}
.andThen { object in
DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .success(object))
}
.catch { error in
DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .failure(error))
}
}

public func load<Query: GraphQLQuery>(query: Query) -> Promise<GraphQLResult<Query.Data>> {
return withinReadTransaction { transaction in
func load<Query: GraphQLQuery>(query: Query) -> Promise<GraphQLResult<Query.Data>> {
return withinReadTransactionPromise { transaction in
let mapper = GraphQLSelectionSetMapper<Query.Data>()
let dependencyTracker = GraphQLDependencyTracker()

Expand All @@ -152,7 +188,7 @@ public final class ApolloStore {
fileprivate let cache: NormalizedCache
fileprivate let cacheKeyForObject: CacheKeyForObject?

fileprivate lazy var loader: DataLoader<CacheKey, Record?> = DataLoader(self.cache.loadRecords)
fileprivate lazy var loader: DataLoader<CacheKey, Record?> = DataLoader(self.cache.loadRecordsPromise)

init(cache: NormalizedCache, cacheKeyForObject: CacheKeyForObject?) {
self.cache = cache
Expand All @@ -168,8 +204,12 @@ public final class ApolloStore {
return try execute(selections: type.selections, onObjectWithKey: key, variables: variables, accumulator: mapper).await()
}

public func loadRecords(forKeys keys: [CacheKey]) -> Promise<[Record?]> {
return cache.loadRecords(forKeys: keys)
public func loadRecords(forKeys keys: [CacheKey],
callbackQueue: DispatchQueue = .main,
completion: @escaping (Result<[Record?], Error>) -> Void) {
self.cache.loadRecords(forKeys: keys,
callbackQueue: callbackQueue,
completion: completion)
}

private final func complete(value: Any?) -> ResultOrPromise<JSONValue?> {
Expand Down Expand Up @@ -249,7 +289,7 @@ public final class ApolloStore {

_ = try executor.execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: normalizer)
.flatMap {
self.cache.merge(records: $0)
self.cache.mergePromise(records: $0)
}.andThen { changedKeys in
if let didChangeKeysFunc = self.updateChangedKeysFunc {
didChangeKeysFunc(changedKeys, nil)
Expand All @@ -258,3 +298,48 @@ public final class ApolloStore {
}
}
}

internal extension NormalizedCache {
func loadRecordsPromise(forKeys keys: [CacheKey]) -> Promise<[Record?]> {
return Promise { fulfill, reject in
self.loadRecords(
forKeys: keys,
callbackQueue: nil) { result in
switch result {
case .success(let records):
fulfill(records)
case .failure(let error):
reject(error)
}
}
}
}

func mergePromise(records: RecordSet) -> Promise<Set<CacheKey>> {
return Promise { fulfill, reject in
self.merge(
records: records,
callbackQueue: nil) { result in
switch result {
case .success(let cacheKeys):
fulfill(cacheKeys)
case .failure(let error):
reject(error)
}
}
}
}

func clearPromise() -> Promise<Void> {
return Promise { fulfill, reject in
self.clear(callbackQueue: nil) { result in
switch result {
case .success(let success):
fulfill(success)
case .failure(let error):
reject(error)
}
}
}
}
}
34 changes: 34 additions & 0 deletions Sources/Apollo/DispatchQueue+Optional.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// DispatchQueue+Optional.swift
// Apollo
//
// Created by Ellen Shapiro on 8/13/19.
// Copyright © 2019 Apollo GraphQL. All rights reserved.
//

import Foundation

public extension DispatchQueue {

static func apollo_performAsyncIfNeeded(on callbackQueue: DispatchQueue?, action: @escaping () -> Void) {
if let callbackQueue = callbackQueue {
// A callback queue was provided, perform the action on that queue
callbackQueue.async {
action()
}
} else {
// Perform the action on the current queue
action()
}
}

static func apollo_returnResultAsyncIfNeeded<T>(on callbackQueue: DispatchQueue?, action: ((Result<T, Error>) -> Void)?, result: Result<T, Error>) {
guard let action = action else {
return
}

self.apollo_performAsyncIfNeeded(on: callbackQueue) {
action(result)
}
}
}
35 changes: 26 additions & 9 deletions Sources/Apollo/InMemoryNormalizedCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,35 @@ public final class InMemoryNormalizedCache: NormalizedCache {
public init(records: RecordSet = RecordSet()) {
self.records = records
}

public func loadRecords(forKeys keys: [CacheKey]) -> Promise<[Record?]> {

public func loadRecords(forKeys keys: [CacheKey],
callbackQueue: DispatchQueue?,
completion: @escaping (Result<[Record?], Error>) -> Void) {
let records = keys.map { self.records[$0] }
return Promise(fulfilled: records)
DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .success(records))
}

public func merge(records: RecordSet) -> Promise<Set<CacheKey>> {
return Promise(fulfilled: self.records.merge(records: records))

public func merge(records: RecordSet,
callbackQueue: DispatchQueue?,
completion: @escaping (Result<Set<CacheKey>, Error>) -> Void) {
let cacheKeys = self.records.merge(records: records)
DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .success(cacheKeys))
}

public func clear() -> Promise<Void> {
records.clear()
return Promise(fulfilled: ())
public func clear(callbackQueue: DispatchQueue?,
completion: ((Result<Void, Error>) -> Void)?) {
self.records.clear()

guard let completion = completion else {
return
}

DispatchQueue.apollo_returnResultAsyncIfNeeded(on: callbackQueue,
action: completion,
result: .success(()))
}
}
35 changes: 25 additions & 10 deletions Sources/Apollo/NormalizedCache.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
public protocol NormalizedCache {

/// Loads records corresponding to the given keys.
/// - returns: A promise that fulfills with an array, with each index containing either the
/// record corresponding to the key at that index or nil if not found.
func loadRecords(forKeys keys: [CacheKey]) -> Promise<[Record?]>

///
/// - Parameters:
/// - keys: The cache keys to load data for
/// - callbackQueue: [optional] An alternate queue to fire the completion closure on. If nil, will fire on the current queue.
/// - completion: A completion closure to fire when the load has completed. If successful, will contain an array. Each index will contain either the record corresponding to the key at the same index in the passed-in array of cache keys, or nil if that record was not found.
func loadRecords(forKeys keys: [CacheKey],
callbackQueue: DispatchQueue?,
completion: @escaping (Result<[Record?], Error>) -> Void)

/// Merges a set of records into the cache.
/// - returns: A promise that fulfills with a set of keys corresponding to *fields* that have
/// changed (i.e. QUERY_ROOT.Foo.myField). These are the same type of keys as are
/// returned by RecordSet.merge(records:).
func merge(records: RecordSet) -> Promise<Set<CacheKey>>
///
/// - Parameters:
/// - records: The set of records to merge.
/// - callbackQueue: [optional] An alternate queue to fire the completion closure on. If nil, will fire on the current queue.
/// - completion: A completion closure to fire when the merge has completed. If successful, will contain a set of keys corresponding to *fields* that have changed (i.e. QUERY_ROOT.Foo.myField). These are the same type of keys as are returned by RecordSet.merge(records:).
func merge(records: RecordSet,
callbackQueue: DispatchQueue?,
completion: @escaping (Result<Set<CacheKey>, Error>) -> Void)

// Clears all records
func clear() -> Promise<Void>
///
/// - Parameters:
/// - callbackQueue: [optional] An alternate queue to fire the completion closure on. If nil, will fire on the current queue.
/// - completion: [optional] A completion closure to fire when the clear function has completed.
func clear(callbackQueue: DispatchQueue?,
completion: ((Result<Void, Error>) -> Void)?)
}

Loading

0 comments on commit 4693ad8

Please sign in to comment.