Fetching Data
Fetching data in a predictable, type-safe way is one of the core features of Apollo iOS. In this guide, you'll learn how to execute an operation against a GraphQL endpoint and use the result in your application.
Prerequisites
This page assumes some familiarity with building GraphQL operations. For a refresher, we recommend reading this guide and practicing running operations in Apollo Sandbox.
Because Apollo iOS uses standard GraphQL syntax, any operation you can run in Sandbox can also be put into the .graphql
files in your project.
Exception: Apollo iOS does requires every query to have a name (even though this isn't required by the GraphQL spec)
This page also assumes that you've already set up Apollo iOS for your application. For help with setup, see the getting started guide.
Defining operations
In Apollo iOS, each operation you execute is represented as an instance of a generated class that implements the GraphQLOperation
protocol. Constructor arguments can be used to define operation variables if needed. You can then pass an operation object to an ApolloClient
to send the operation to the server, execute it, and receive strongly typed results.
GraphQL operations can be queries, mutations, or subscriptions. For more information on using each of these operation types, see their individual usages guides.
To generate these classes, we first need to define the GraphQL operations we want to execute.
For more information about how Apollo iOS generates your operation classes, see Code Generation.
Let's say we define a GraphQL query named HeroName
:
1query HeroName {
2 hero {
3 id
4 name
5 }
6}
Apollo iOS will generate a HeroNameQuery
class that you can construct and pass to ApolloClient.fetch(query:)
:
1apollo.fetch(query: HeroNameQuery()) { result in
2 guard let data = try? result.get().data else { return }
3 print(data.hero.name) // Luke Skywalker
4}
To learn about defining operations that take arguments, see Operation Arguments.
Generated operation models
An operation's results are returned as a hierarchy of immutable structs that match the structure of the operations's fields. These structs only include fields that are included in the operation (other schema fields are omitted).
In other words, Apollo iOS generates result types based on the operations you write, not based on the schema you query against.
For example, given the following schema:
1type Query {
2 hero: Character!
3}
4
5interface Character {
6 id: String!
7 name: String!
8 friends: [Character!]
9 appearsIn: [Episode]!
10 }
11
12 type Human implements Character {
13 id: String!
14 name: String!
15 friends: [Character]
16 appearsIn: [Episode]!
17 height(unit: LengthUnit = METER): Float
18 }
19
20 type Droid implements Character {
21 id: String!
22 name: String!
23 friends: [Character]
24 appearsIn: [Episode]!
25 primaryFunction: String
26}
And the following query:
1query HeroAndFriendsNames {
2 hero {
3 id
4 name
5 friends {
6 id
7 name
8 }
9 }
10}
Apollo iOS generates a type-safe model that looks something like this (details are omitted to focus on the class structure):
1class HeroAndFriendsNamesQuery: GraphQLQuery {
2 struct Data: SelectionSet {
3 let hero: Hero
4
5 struct Hero: SelectionSet {
6 let id: String
7 let name: String
8 let friends: [Friend]?
9
10 struct Friend: SelectionSet {
11 let id: String
12 let name: String
13 }
14 }
15 }
16}
Because the HeroAndFriendsNames
query doesn't fetch appearsIn
, this property is not part of the returned result type and cannot be accessed here. Similarly, id
is only accessible in Friend
, not in Hero
.
Because GraphQL supports nullability, you have compile-time type safety. If the request is successful, all queried data (and only this data) will be accessible. There is no need to handle null fields in UI code.
For more information on how to fetch type-safe data, learn about type conditions.
Operation result handling
The result of executing an operation is a Swift Result
whose .success
case contains a GraphQLResult<Data>
where Data
is the generated root Data
struct of the operation that was executed.
You can call try result.get().data
to obtain the Data
object from the result. If you would like to handle error's or inspect the result's metadata, you can switch
on the result like this:
1apollo.fetch(query: HeroNameQuery()) { result in
2 switch result {
3 case .success(let graphQLResult):
4 if let name = graphQLResult.data?.hero?.name {
5 print(name) // Luke Skywalker
6 } else if let errors = graphQLResult.errors {
7 // GraphQL errors
8 print(errors)
9 }
10 case .failure(let error):
11 // Network or response format errors
12 print(error)
13 }
14}
Note: An operation can be successful, but the
GraphQLResult
may still includeGraphQLErrors
. See Error Handling for more information.
Handling operation results in the background
By default, Apollo will deliver operation results on the main thread, which is probably what you want if you're using them to update the UI.
If you want your result handler to be called on a background queue, the fetch(query:)
, perform(mutation:)
and subscribe(subscription:)
functions take an optional queue:
parameter.
1apollo.fetch(
2 query: HeroNameQuery(),
3 queue: DispatchQueue.global(qos: .background),
4) { result in
5 ... // Will be called on a background queue
6}
Fetching locally cached data
Apollo iOS uses a normalized cache to store your GraphQL response data locally. This allows you to retrieve operation data that has been previously fetched without waiting for an additional network request. You can configure how your fetch requests interact with the cache using cache policies.
Cache policies
ApolloClient
's fetch(query:)
method takes an optional cachePolicy
that allows you to specify when results should be fetched from the server, and when data should be loaded from the local cache.
By default, each request uses the .returnCacheDataElseFetch
cache policy, which means data will be loaded from the cache when available, and fetched from the server otherwise.
The cache polices which you can specify are:
.fetchIgnoringCacheData
Always fetch from the server, but still store results to the cache.
.fetchIgnoringCacheCompletely
Always fetch from the server, and do not store results from the cache.
If you're not using the cache at all, this method is preferred to
fetchIgnoringCacheData
for performance reasons.
.returnCacheDataAndFetch
Return data from the cache if its available; then perform a fetch to see if there are any updates; store results in the cache.
This is useful if you are watching queries, since those will be updated when the call to the server returns.
.returnCacheDataDontFetch
Return data from the cache; never fetch from the server.
This policy will return an error if cached data is not available.
.returnCacheDataElseFetch
- (Default Value)Return data from the cache if its available; if cache data is missing or incomplete, fetch data from the server and store results in the cache.
This is the most common use case, and is the most performant method if your data is not expected to change.
If you do not need to check for updates to data once it's been fetched, you should usually use this cache policy.
If you're interested in returning cached data after a failed fetch, the current recommended approach is to use an
additionalErrorInterceptor
on your interceptor chain to examine if the error is one it makes sense to show old data for rather than something that needs to be passed on to the user, and then retrying with a.returnCacheDataDontFetch
retry policy. An example of this setup can be found in the Cache-dependent interceptor tests.
For more information on how result data is stored in the cache data or how to configure the normalized cache, see the Caching documentation.
Using GET
instead of POST
for queries
By default, Apollo constructs queries and sends them to your graphql endpoint using POST
with the JSON generated.
If you want Apollo to use GET
instead, pass true
to the optional useGETForQueries
parameter when setting up your RequestChainNetworkTransport
. This will set up all queries conforming to GraphQLQuery
sent through the HTTP transport to use GET
.
NOTE: This is a toggle which affects all queries sent through that client, so if you need to have certain queries go as
POST
and certain ones go asGET
, you will likely have to swap out theRequestChainNetworkTransport
.