Skip to content

Commit

Permalink
make ApolloStore an open class, and its dependencies are public
Browse files Browse the repository at this point in the history
  • Loading branch information
lincolnq committed Oct 16, 2024
1 parent fe0b7a8 commit 5b2f32b
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 89 deletions.
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

0 comments on commit 5b2f32b

Please sign in to comment.