Docs
Try Apollo Studio

Direct cache access


Apollo iOS provides the ability to directly read and update the cache as needed using type-safe generated operation models. This provides a strongly-typed interface for accessing your cache data in pure Swift code.

The ApolloStore has APIs for accessing the cache via both ReadTransaction and ReadWriteTransaction.

This article explains how you can access cache data directly. To learn how you can use the cache alongside network requests to fetch data for your GraphQL operations, read our documentation on fetching locally cached data.

Reading cache data

You can read data from the local cache directly with ApolloStore.withinReadTransaction(_:callbackQueue:completion:). The transaction block provides a ReadTransaction.

A ReadTransaction can be used to read any of your generated GraphQL queries or fragments.

// Read from a GraphQL Query
client.store.withinReadTransaction({ transaction in
let data = try transaction.read(
query: HeroNameQuery(episode: .jedi)
)
print(data.hero?.name)
})
// Read from a GraphQL fragment
client.store.withinReadTransaction({ transaction -> HeroDetails in
let data = try transaction.readObject(
ofType: HeroDetails.self,
withKey: "Hero:123"
)
print(data.hero?.name)
})

Writing cache data

You can write data to the local cache with ApolloStore.withinReadWriteTransaction(_:callbackQueue:completion:). The transaction block provides a ReadWriteTransaction. In addition the the ability to write data to the cache, ReadWriteTransaction has all of the functionality of a ReadTransaction. To write data to the cache, you'll need to define a LocalCacheMutation.

Defining local cache mutations

Like reading cache data, writing to the cache uses type-safe generated models. However, because the generated models for your operations and fragments are immutable, you cannot change the values on these models to write them to the cache. In order to write to the cache, you can define a LocalCacheMutation.

A LocalCacheMutation is just a GraphQL query or fragment definition that has been flagged as a local cache mutation using the Apollo iOS specific directive @apollo_client_ios_localCacheMutation.

When a query or fragment with this directive is defined, the code generation engine will generate a mutable model that can be used with a ReadWriteTransaction to write data to the cache.

Your query definitions can also define variables for these operations to mutate cache data for fields with input arguments. For more information see our operation argument documentation.

Mutable Query Definition
query HeroNameLocalCacheMutation(
$episode: Episode!
) @apollo_client_ios_localCacheMutation {
hero(episode: $episode) {
id
name
}
}
Mutable Fragment Definition
fragment MutableHeroDetails on Hero
@apollo_client_ios_localCacheMutation {
id
name
}

The generated models for your mutations will have mutable fields (var instead of let). Generating both getters and setters for fields on the mutable models means that they are larger than immutable generated models.

Seperating Cache Mutations from Network Operations

The generated models for queries defined as cache mutations will conform to LocalCacheMutation, but not GraphQLQuery. This means they cannot be sent as query operations to your server using an ApolloClient. Because network response data is immutable and cache mutation models are mutable, you must use seperate models.

Because mutable data requires a lot more generated code, generating mutable models for all operations would nearly double the size of the generated operations. Additionally, if the models were mutable, mutating them outside of a ReadWriteTransaction would not persist any changes to the cache. By maintaining immutable models, we avoid any confusion this could cause.

Cache mutations are designed to be narrowly scoped to access and mutate only the necessary data. You should avoid creating mutable versions of entire query operations. Instead, define mutable fragments or queries to mutate only the necessary fields.

Writing local cache mutations to the cache

Once you have generated mutable query models, you can use them with ReadWriteTransaction.update().

Local Cache Mutation Update
store.withinReadWriteTransaction({ transaction in
let cacheMutation = HeroNameLocalCacheMutation(episode: .CLONES)
try transaction.update(cacheMutation) { (data: inout HeroNameLocalCacheMutation.Data) in
data.hero.name = "General Kenobi"
}
let queryData = try transaction.read(
query: HeroNameQuery(episode: .jedi)
)
print(queryData.hero?.name) // "General Kenobi"
})

Writing mutable fragments to the cache

To write data for a mutable fragment, use ReadWriteTransaction.updateObject(ofType:withKey:variables:_:). You will need to pass the cache key of the object you want to update along with the mutable fragment.

For more information on cache keys in the normalized cache, check out our documentation on normalized cache responses.

Mutable Fragment Update
store.withinReadWriteTransaction({ transaction in
try transaction.updateObject(
ofType MutableHeroDetails.self,
withKey: "Hero:123"
) { (data: inout MutableHeroDetails) in
data.hero.name = "General Kenobi"
let queryData = try transaction.read(
query: HeroNameQuery(episode: .jedi)
)
print(queryData.hero?.name) // "General Kenobi"
}
})

Deleting cache data

There are presently three deletion methods available:

  1. ApolloStore.clear

    Removes all data from the cache immediately.

  2. ReadWriteTransaction.removeObject(for:)

    Removes a single object for the given CacheKey.

  3. ReadWriteTransaction.removeObjects(matching:)

    Removes all objects with a CacheKey that matches the given pattern. Pattern matching is not case sensitive. For an in memory cache it is the equivalent of checking whether the cache key contains the pattern and SQLite caches will perform a LIKE query to remove the objects. This method can be very slow depending on the number of records in the cache, it is recommended that this method be called on a background queue.

The removeObject(for:) and removeObjects(matching:) functions will only remove things at the level of an object. They cannot remove individual properties from an object, and cannot remove outside references to an object.

Deleting an object from the cache does not perform cascading deletions. That is, if you remove an object which has a reference to another object, the reference will be removed, but that other object will not be removed and will remain in the cache. Likewise, if you delete an object, references to that object will not be deleted, they will simply fail, causing a cache miss, when you attempt to load the object again.

This means that if you are planning to remove something, be sure that you either:

A) Know for sure you no longer need it

or

B) Are fine with your cache policy potentially triggering an additional fetch if the missing value causes a read to fail

Note: As of right now, there is not a way to delete a single property's value. For instance, calling try transaction.removeRecord(for: "2001.name") will result in no action, as there would not be a record with the cache key "2001.name", since name is a scalar field on the "2001" record.

Deleting objects by cache key

You will need to have a clear understanding of how you are generating cache keys to be able to use this functionality effectively. To help you figure out how to construct a cache key or pattern to delete, we recommend having a clear understanding of how cache keys are generated and configuring custom cache keys for your cache objects.

Removing objects by their cache key allows you to clear stale or unneeded cache data without deleting the entire cache.

In Apollo iOS, you can configure custom cache keys for your objects. The key for an entry in the cache that uses custom cache IDs will have the format `${ObjectType}:${CustomCacheKey).

In this example, we've configured our cache to attempt to use the id field as a cache ID.

SchemaConfiguration.swift
static func cacheKeyInfo(for type: Object, object: JSONObject) -> CacheKeyInfo? {
try? CacheKeyInfo(jsonValue: object["id"])
}

This indicates that for every object which has an id field, its cache ID will be the value of the id field.

If you have previously fetched a Book object with the ID "2001", the key for that entry in the cache will be "Book:2001". To delete this object from the cache, you can call transaction.removeObject(for: "Book:2001") within a ReadWriteTransaction. This will remove the cache entry with that key, along with all of its associated scalar properties and the references to other objects it stores.

We can also delete all objects of a concrete object type from the cache. To remove all Book objects, you can use transaction.removeObjects(matching: "Book:").

Edit on GitHub
Previous
Setup
Next
Custom Cache Keys