Using the @defer directive in Apollo iOS
⚠️ The
@defer
directive is currently an experimental feature in Apollo iOS and is available for use from version 1.14.0. If you have feedback on the feature, please let us know via a new GitHub issue or search through any existing@defer
-related issues.
The @defer
directive enables your queries to receive data for specific fields asynchronously. This is helpful whenever some fields in a query take much longer to resolve than the others.
For example, let's say we're building a social media application that can quickly fetch a user's basic profile information, but retrieving that user's friends takes longer. If we include all of those fields in a single query we want to be able to display the basic profile information as soon as it's available instead of waiting for the friends
field to resolve.
To achieve this, we can apply the @defer
directive to an in-line fragment that contains all slow-resolving fields related to friend
data:
1query PersonQuery($personId: ID!) {
2 person(id: $personId) {
3 # Basic fields (fast)
4 id
5 firstName
6 lastName
7
8 # Friend fields (slower)
9 ... on User @defer(label: "deferredFriends") {
10 friends {
11 id
12 }
13 }
14 }
15}
In the generated code for this query the asUser
type case will be optional, it will also have a fragments
container in which there will be an optional fragment with the same name as the label
property value used in the query. The deferred fragment is optional because it will not be returned with the basic fields, it will be returned in a separate response instead.
All deferred fragments are annotated with the @Deferred
property wrapper. This allows you to check the state of any deferred fragment through it's projected value. When the fields of the deferred fragment are received, another response will be returned with all basic fields and friends
fields too.
1client.fetch(query: ExampleQuery()) { result in
2 switch (result) {
3 case let .success(data):
4 if case .fulfilled = data.data?.person.asUser?.fragments.$deferredFriends {
5 print("Query Success! Received all fields.")
6 } else {
7 print("Query Success! Received basic fields only.")
8 }
9 case let .failure(error):
10 print("Query Failure! \(error)")
11 }
12}
Will print something like this:
1Query Success! Received basic fields only.
2Query Success! Received all fields.
Requirements
The @defer directive must be used on a type case.
The @defer directive must have a
label
property.
Caching
Reading cache data
Typical caching behaviour and functionality is supported for operations with the @defer
directive too. Cache reads behave slightly differently to network requests in that all cached data, including any cached deferred fragments, will be returned in the first response. In other words, if the entire query has been cached then there will only be one response with all basic and deferred fragments. If you manually added only the basic data to the cache then you would receive only the basic data in the first response and depending on the cache policy used may get the deferred fragments returned from the network separately.
Writing cache data
We're still building Selection Set Initializer compatibility for deferred fragments. Until that is complete, writing to the cache requires that the local cache mutation, or named fragment selection set, be initialized using the private .init(_dataDict: DataDict)
function.