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

Make ApolloStore an open extensible class, and its previously-internal dependencies public #509

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
43 changes: 23 additions & 20 deletions apollo-ios/Sources/Apollo/ApolloStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ public protocol ApolloStoreSubscriber: AnyObject {
}

/// The `ApolloStore` class acts as a local cache for normalized GraphQL results.
public class ApolloStore {
private let cache: any NormalizedCache
private let queue: DispatchQueue
open class ApolloStore {
public let cache: any NormalizedCache
public let queue: DispatchQueue

internal var subscribers: [any ApolloStoreSubscriber] = []
open var subscribers: [any ApolloStoreSubscriber] = []

/// Designated initializer
/// - Parameters:
Expand All @@ -36,7 +36,7 @@ public class ApolloStore {
self.queue = DispatchQueue(label: "com.apollographql.ApolloStore", attributes: .concurrent)
}

fileprivate func didChangeKeys(_ changedKeys: Set<CacheKey>, identifier: UUID?) {
open func didChangeKeys(_ changedKeys: Set<CacheKey>, identifier: UUID?) {
for subscriber in self.subscribers {
subscriber.store(self, didChangeKeys: changedKeys, contextIdentifier: identifier)
}
Expand All @@ -47,7 +47,7 @@ public class ApolloStore {
/// - Parameters:
/// - callbackQueue: The queue to call the completion block on. Defaults to `DispatchQueue.main`.
/// - completion: [optional] A completion block to be called after records are merged into the cache.
public func clearCache(callbackQueue: DispatchQueue = .main, completion: ((Result<Void, any Swift.Error>) -> Void)? = nil) {
open func clearCache(callbackQueue: DispatchQueue = .main, completion: ((Result<Void, any Swift.Error>) -> Void)? = nil) {
queue.async(flags: .barrier) {
let result = Result { try self.cache.clear() }
DispatchQueue.returnResultAsyncIfNeeded(
Expand All @@ -65,7 +65,7 @@ public class ApolloStore {
/// to assist in de-duping cache hits for watchers.
/// - callbackQueue: The queue to call the completion block on. Defaults to `DispatchQueue.main`.
/// - completion: [optional] A completion block to be called after records are merged into the cache.
public func publish(records: RecordSet, identifier: UUID? = nil, callbackQueue: DispatchQueue = .main, completion: ((Result<Void, any Swift.Error>) -> Void)? = nil) {
open func publish(records: RecordSet, identifier: UUID? = nil, callbackQueue: DispatchQueue = .main, completion: ((Result<Void, any Swift.Error>) -> Void)? = nil) {
queue.async(flags: .barrier) {
do {
let changedKeys = try self.cache.merge(records: records)
Expand All @@ -90,7 +90,7 @@ public class ApolloStore {
/// - Parameters:
/// - subscriber: A subscriber to receive content change notificatons. To avoid a retain cycle,
/// ensure you call `unsubscribe` on this subscriber before it goes out of scope.
public func subscribe(_ subscriber: any ApolloStoreSubscriber) {
open func subscribe(_ subscriber: any ApolloStoreSubscriber) {
queue.async(flags: .barrier) {
self.subscribers.append(subscriber)
}
Expand All @@ -101,7 +101,7 @@ public class ApolloStore {
/// - Parameters:
/// - subscriber: A subscribe that has previously been added via `subscribe`. To avoid retain cycles,
/// call `unsubscribe` on all active subscribers before they go out of scope.
public func unsubscribe(_ subscriber: any ApolloStoreSubscriber) {
open func unsubscribe(_ subscriber: any ApolloStoreSubscriber) {
queue.async(flags: .barrier) {
self.subscribers = self.subscribers.filter({ $0 !== subscriber })
}
Expand All @@ -113,7 +113,7 @@ public class ApolloStore {
/// - 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>(
open func withinReadTransaction<T>(
_ body: @escaping (ReadTransaction) throws -> T,
callbackQueue: DispatchQueue? = nil,
completion: ((Result<T, any Swift.Error>) -> Void)? = nil
Expand Down Expand Up @@ -143,7 +143,7 @@ public class ApolloStore {
/// - 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>(
open func withinReadWriteTransaction<T>(
_ body: @escaping (ReadWriteTransaction) throws -> T,
callbackQueue: DispatchQueue? = nil,
completion: ((Result<T, any Swift.Error>) -> Void)? = nil
Expand Down Expand Up @@ -172,7 +172,7 @@ public class ApolloStore {
/// - Parameters:
/// - query: The query to load results for
/// - resultHandler: The completion handler to execute on success or error
public func load<Operation: GraphQLOperation>(
open func load<Operation: GraphQLOperation>(
_ operation: Operation,
callbackQueue: DispatchQueue? = nil,
resultHandler: @escaping GraphQLResultHandler<Operation.Data>
Expand Down Expand Up @@ -201,18 +201,20 @@ public class ApolloStore {
}

public class ReadTransaction {
fileprivate let cache: any NormalizedCache

fileprivate lazy var loader: DataLoader<CacheKey, Record> = DataLoader { [weak self] batchLoad in
public let cache: any NormalizedCache

@_spi(Execution)
public lazy var loader: DataLoader<CacheKey, Record> = DataLoader { [weak self] batchLoad in
guard let self else { return [:] }
return try cache.loadRecords(forKeys: batchLoad)
}

fileprivate lazy var executor = GraphQLExecutor(
@_spi(Execution)
public lazy var executor = GraphQLExecutor(
executionSource: CacheDataExecutionSource(transaction: self)
)

fileprivate init(store: ApolloStore) {
public init(store: ApolloStore) {
self.cache = store.cache
}

Expand All @@ -237,7 +239,8 @@ public class ApolloStore {
)
}

func readObject<SelectionSet: RootSelectionSet, Accumulator: GraphQLResultAccumulator>(
@_spi(Execution)
public func readObject<SelectionSet: RootSelectionSet, Accumulator: GraphQLResultAccumulator>(
ofType type: SelectionSet.Type,
withKey key: CacheKey,
variables: GraphQLOperation.Variables? = nil,
Expand All @@ -264,9 +267,9 @@ public class ApolloStore {

public final class ReadWriteTransaction: ReadTransaction {

fileprivate var updateChangedKeysFunc: DidChangeKeysFunc?
public let updateChangedKeysFunc: DidChangeKeysFunc?

override init(store: ApolloStore) {
public override init(store: ApolloStore) {
self.updateChangedKeysFunc = store.didChangeKeys
super.init(store: store)
}
Expand Down
3 changes: 2 additions & 1 deletion apollo-ios/Sources/Apollo/DataLoader.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
final class DataLoader<Key: Hashable, Value> {
@_spi(Execution)
public final class DataLoader<Key: Hashable, Value> {
public typealias BatchLoad = (Set<Key>) throws -> [Key: Value]
private var batchLoad: BatchLoad

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import ApolloAPI
/// A `GraphQLExecutionSource` configured to execute upon the data stored in a ``NormalizedCache``.
///
/// Each object exposed by the cache is represented as a `Record`.
struct CacheDataExecutionSource: GraphQLExecutionSource {
typealias RawObjectData = Record
typealias FieldCollector = CacheDataFieldSelectionCollector
@_spi(Execution)
public struct CacheDataExecutionSource: GraphQLExecutionSource {
public typealias RawObjectData = Record
public typealias FieldCollector = CacheDataFieldSelectionCollector

/// A `weak` reference to the transaction the cache data is being read from during execution.
/// This transaction is used to resolve references to other objects in the cache during field
Expand All @@ -24,13 +25,13 @@ struct CacheDataExecutionSource: GraphQLExecutionSource {
/// When executing on cache data all selections, including deferred, must be executed together because
/// there is only a single response from the cache data. Any deferred selection that was cached will
/// be returned in the response.
var shouldAttemptDeferredFragmentExecution: Bool { true }
public var shouldAttemptDeferredFragmentExecution: Bool { true }

init(transaction: ApolloStore.ReadTransaction) {
self.transaction = transaction
}

func resolveField(
public func resolveField(
with info: FieldExecutionInfo,
on object: Record
) -> PossiblyDeferred<AnyHashable?> {
Expand Down Expand Up @@ -72,14 +73,14 @@ struct CacheDataExecutionSource: GraphQLExecutionSource {
return transaction.loadObject(forKey: reference.key)
}

func computeCacheKey(for object: Record, in schema: any SchemaMetadata.Type) -> CacheKey? {
public func computeCacheKey(for object: Record, in schema: any SchemaMetadata.Type) -> CacheKey? {
return object.key
}

/// A wrapper around the `DefaultFieldSelectionCollector` that maps the `Record` object to it's
/// `fields` representing the object's data.
struct CacheDataFieldSelectionCollector: FieldSelectionCollector {
static func collectFields(
public struct CacheDataFieldSelectionCollector: FieldSelectionCollector {
public static func collectFields(
from selections: [Selection],
into groupedFields: inout FieldSelectionGrouping,
for object: Record,
Expand Down
25 changes: 14 additions & 11 deletions apollo-ios/Sources/Apollo/GraphQLDependencyTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,47 @@
import ApolloAPI
#endif

final class GraphQLDependencyTracker: GraphQLResultAccumulator {
@_spi(Execution)
public final class GraphQLDependencyTracker: GraphQLResultAccumulator {

let requiresCacheKeyComputation: Bool = true
public let requiresCacheKeyComputation: Bool = true

private var dependentKeys: Set<CacheKey> = Set()

public init() {}

func accept(scalar: JSONValue, info: FieldExecutionInfo) {
public func accept(scalar: JSONValue, info: FieldExecutionInfo) {
dependentKeys.insert(info.cachePath.joined)
}

func accept(customScalar: JSONValue, info: FieldExecutionInfo) {
public func accept(customScalar: JSONValue, info: FieldExecutionInfo) {
dependentKeys.insert(info.cachePath.joined)
}

func acceptNullValue(info: FieldExecutionInfo) {
public func acceptNullValue(info: FieldExecutionInfo) {
dependentKeys.insert(info.cachePath.joined)
}

func acceptMissingValue(info: FieldExecutionInfo) throws -> () {
public func acceptMissingValue(info: FieldExecutionInfo) throws -> () {
dependentKeys.insert(info.cachePath.joined)
}

func accept(list: [Void], info: FieldExecutionInfo) {
public func accept(list: [Void], info: FieldExecutionInfo) {
dependentKeys.insert(info.cachePath.joined)
}

func accept(childObject: Void, info: FieldExecutionInfo) {
public func accept(childObject: Void, info: FieldExecutionInfo) {
}

func accept(fieldEntry: Void, info: FieldExecutionInfo) -> Void? {
public func accept(fieldEntry: Void, info: FieldExecutionInfo) -> Void? {
dependentKeys.insert(info.cachePath.joined)
return ()
}

func accept(fieldEntries: [Void], info: ObjectExecutionInfo) {
public func accept(fieldEntries: [Void], info: ObjectExecutionInfo) {
}

func finish(rootValue: Void, info: ObjectExecutionInfo) -> Set<CacheKey> {
public func finish(rootValue: Void, info: ObjectExecutionInfo) -> Set<CacheKey> {
return dependentKeys
}
}
Loading