Docs
Try Apollo Studio

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:

query HeroName {
hero {
id
name
}
}

Apollo iOS will generate a HeroNameQuery class that you can construct and pass to ApolloClient.fetch(query:):

apollo.fetch(query: HeroNameQuery()) { result in
guard let data = try? result.get().data else { return }
print(data.hero.name) // Luke Skywalker
}

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:

type Query {
hero: Character!
}
interface Character {
id: String!
name: String!
friends: [Character!]
appearsIn: [Episode]!
}
type Human implements Character {
id: String!
name: String!
friends: [Character]
appearsIn: [Episode]!
height(unit: LengthUnit = METER): Float
}
type Droid implements Character {
id: String!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}

And the following query:

query HeroAndFriendsNames {
hero {
id
name
friends {
id
name
}
}
}

Apollo iOS generates a type-safe model that looks something like this (details are omitted to focus on the class structure):

class HeroAndFriendsNamesQuery: GraphQLQuery {
struct Data: SelectionSet {
let hero: Hero
struct Hero: SelectionSet {
let id: String
let name: String
let friends: [Friend]?
struct Friend: SelectionSet {
let id: String
let name: String
}
}
}
}

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:

apollo.fetch(query: HeroNameQuery()) { result in
switch result {
case .success(let graphQLResult):
if let name = graphQLResult.data?.hero?.name {
print(name) // Luke Skywalker
} else if let errors = graphQLResult.errors {
// GraphQL errors
print(errors)
}
case .failure(let error):
// Network or response format errors
print(error)
}
}

Note: An operation can be successful, but the GraphQLResult may still include GraphQLErrors. 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.

apollo.fetch(
query: HeroNameQuery(),
queue: DispatchQueue.global(qos: .background),
) { result in
... // Will be called on a background queue
}

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 as GET, you will likely have to swap out the RequestChainNetworkTransport.

Edit on GitHub
Previous
Running Code Generation in Swift Code
Next
Queries