Custom cache keys
When working with a normalized cache, it is recommended that you specify a cache ID for each object type in your schema. If you don't, objects are assigned a default cache ID, but that ID can lead to undesirable duplication of data.
The normalized cache computes a cache key for each object that is stored in the cache. With Apollo iOS, you can customize the computation of cache keys to improve the performance and capabilities of your cache.
To learn more, read about how the cache normalizes objects by cache key.
For most use cases, you can configure cache keys declaratively using the methods described on this page. For advanced use cases not supported by declarative cache keys, you can configure cache keys programmatically.
Schema extensions
Declarative cache keys work by adding directives to your backend schema types to indicate how cache keys should be resolved for those types. To do this, you can extend your backend schema by creating a schema extension file with the file extension .graphqls
. You can add any number of schema extension files to your project.
schemaSearchPaths
of your code generation configuration's FileInput
.The @typePolicy
directive
The @typePolicy
directive lets you specify an object's cache ID using key fields of the object returned by your GraphQL server. You can apply @typePolicy
to both concrete object types and interface types in your schema.
To declare a type policy, extend the type with the @typePolicy
directive in a .graphqls
schema extension file.
Key fields
The @typePolicy
directive has a single keyFields
argument, which takes a string indicating the fields used to determine a type's cache key.
extend type Book @typePolicy(keyFields: "id")
If you add the above schema extension file , Apollo iOS resolves the cache key for all objects with a __typename
of "Book"
by using the value of their id
field. The concrete type of the object is always prepended to the cache key. This means that a Book
object with an id
of 456
will resolve to have a cache key of Book:456
.
@typePolicy
key fields must return a scalar type. To use non-scalar fields in cache keys, use programmatic cache key configuration.Multiple key fields
You can specify multiple key fields for an object if they are all required to uniquely identify a particular cache entry. Separate multiple key fields with a single space when declaring them:
1extend type Author @typePolicy(keyFields: "firstName lastName")
With this extension, the resolved cache key includes all declared key fields and separates them by the +
character. In this case, an Author
object with a firstName
of "Ray"
and a last name of "Bradbury"
would resolve to have a cache key of Author:Ray+Bradbury
.
Type policies on interface types
You can also use @typePolicy
on interface types. Doing so specifies a default set of key fields for all types that implement that interface.
1extend interface Node @typePolicy(keyFields: "id")
@typePolicy
, it must match any type policies of any interfaces the object type implements.Caveats and limitations
Declarative cache key resolution has a few limitations you should be aware of while implementing your @typePolicy
directives:
Conflicting Type Policies
If any type implements two interfaces with conflicting type policies, Apollo iOS throws a validation error when executing code generation.
Cache Inconsistencies
When an object is returned for a field of an interface type where the interface has a @typePolicy
, Apollo iOS will first attempt to find a @typePolicy
for the concrete type by using the __typename
field of the returned object. If the type does not have an @typePolicy
of its own, the interface's @typePolicy
will be applied.
If the same object is returned for multiple fields of different interface types with conflicting type policies, it is possible that the same object is resolved with two different type policies, leading to cache inconsistencies.
In most circumstances, validation that is run during code generation prevents this from occurring. However, this may still occur if changes are made to the schema after code generation runs or after your application is published.
This is possible if:
A) A type is added to your schema after code generation is executed and that type implements two different interfaces with different type policies
B) Implementation of an interface is added to an existing type which already implements an interface with a different type policy