Fetching queries


Note: This page is about using Apollo iOS to fetch and access GraphQL query results. You can read about GraphQL queries themselves in detail at graphql.org.

When using Apollo iOS, you don't have to learn anything special about the query syntax, since everything is just standard GraphQL. Anything you can type into the GraphiQL query explorer, you can also put into .graphql files in your project.

Apollo iOS takes a schema and a set of .graphql files and uses these to generate code you can use to execute queries and access typed results.

All .graphql files in your project (or the subset you specify as input to apollo if you customize the script you define as the code generation build phase) will be combined and treated as one big GraphQL document.

That means fragments defined in one .graphql file are available to all other .graphql files for example, but it also means operation names and fragment names must be unique and you will receive validation errors if they are not.

Creating queries

Queries are represented as instances of generated classes conforming to the GraphQLQuery protocol. Constructor arguments can be used to define query variables if needed. You pass a query object to ApolloClient#fetch(query:) to send the query to the server, execute it, and receive typed results.

For example, if you define a query called HeroName:

GraphQL
1query HeroName($episode: Episode) {
2  hero(episode: $episode) {
3    name
4  }
5}

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

Swift
1apollo.fetch(query: HeroNameQuery(episode: .empire)) { result in
2  guard let data = try? result.get().data else { return }
3  print(data.hero?.name) // Luke Skywalker
4}

By default, Apollo will deliver query results on the main thread, which is probably what you want if you're using them to update the UI. fetch(query:) takes an optional queue: parameter however, if you want your result handler to be called on a background queue.

To handle potential errors, check the failure(Error) result case, which details network or response format errors (such as invalid JSON):

Swift
1apollo.fetch(query: HeroNameQuery(episode: .empire)) { 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}

In addition to an optional data property, success(Success) result case contains an optional errors array with GraphQL errors (for more on this, see the sections on response format errors in the GraphQL specification).

Typed query results

Query results are defined as nested immutable structs that at each level only contain the properties defined in the corresponding part of the query definition. This means the type system won't allow you to access fields that are not actually fetched by the query, even if they are part of the schema.

For example, given the following schema:

GraphQL
1enum Episode { NEWHOPE, EMPIRE, JEDI }
2
3interface Character {
4  id: String!
5  name: String!
6  friends: [Character]
7  appearsIn: [Episode]!
8 }
9
10 type Human implements Character {
11   id: String!
12   name: String!
13   friends: [Character]
14   appearsIn: [Episode]!
15   height(unit: LengthUnit = METER): Float
16 }
17
18 type Droid implements Character {
19   id: String!
20   name: String!
21   friends: [Character]
22   appearsIn: [Episode]!
23   primaryFunction: String
24}

And the following query:

GraphQL
1query HeroAndFriendsNames($episode: Episode) {
2  hero(episode: $episode) {
3    name
4    friends {
5      name
6    }
7  }
8}

You can fetch results and access data using the following code:

Swift
1apollo.fetch(query: HeroAndFriendsNamesQuery(episode: .empire)) { result in
2  guard let data = try? result.get().data else { return }
3  print(data.hero?.name) // Luke Skywalker
4  print(data.hero?.friends?.flatMap { $0?.name }.joined(separator: ", "))
5  // Prints: Han Solo, Leia Organa, C-3PO, R2-D2
6}

Because the above query won't fetch appearsIn, this property is not part of the returned result type and cannot be accessed here.

Notes on working with Custom Scalars

Custom scalars are types defined by your schema that are based on other GraphQL scalar types (such as String or Int). Without intervention, code generation will use the underlying types to generate code for the custom scalars.

If you want to use the custom scalars within your code, you must set passthroughCustomScalars to true either at the command line or using Swift Scripting.

Once you've done that, you can either create your own type locally or use a typealias to declare an equivilent. This is very, very frequently used with Date types. Please see the Custom Scalar Playground Page for a full example using a custom date type.

JSON and other Custom Scalars with multiple return types

Some custom scalars are set up to potentially return multiple types at runtime. This is not ideal since you lose type safety, but if you're using an API you don't have control over, there's often not a great alternative to this.

When this happens, because you don't know the type that's coming in, you can't set up a single typealias for that scalar. Instead, you need to define some other way of instantiating your custom scalar object.

This happens most often with JSON, which can return either an array or a dictionary. Here's an example of how you can use an enum to allow dynamic-but-limited types to parse (with CustomJSON as a placeholder type name`):

Swift
1enum CustomJSON {
2  case dictionary([String: Any])
3  case array([Any])
4}
5
6extension CustomJSON: JSONDecodable {
7  init(jsonValue value: JSONValue) throws {
8    if let dict = value as? [String: Any] {
9      self = .dictionary(dict)
10    } else if let array = value as? [Any] {
11      self = .array(array)
12    } else {
13      throw JSONDecodingError.couldNotConvert(value: value, to: CustomJSON.self)
14    }
15  }
16}

Again, make sure to define this in a file that is outside of your generated code, or it will get overwritten.

Specifying a cache policy

This section has moved to 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.

JSON serialization

The classes generated by Apollo iOS can be converted to JSON using their jsonObject property. This may be useful for conveniently serializing GraphQL instances for storage in a database, or a file.

For example:

Swift
1apollo.fetch(query: HeroAndFriendsNamesQuery(episode: .empire)) { result in
2  guard let data = try? result.get().data else { return }
3
4  // Serialize the response as JSON
5  let json = data.jsonObject
6  let serialized = try! JSONSerialization.data(withJSONObject: json, options: [])
7  
8  // Deserialize the response
9  let deserialized = try! JSONSerialization.jsonObject(with: serialized, options: []) as! JSONObject
10  let heroAndFriendsNames = try! HeroAndFriendsNamesQuery.Data(jsonObject: deserialized)
11}

Automatic Persisted Queries

Apollo Server allows you to use a feature called Automatic Persisted Queries, or APQs, to needing to resend large query documents over and over.

Each query or mutation is identified by the SHA256 hash of its contents. If the hash can't be found by the server, it sends back an error indicating that it needs the full query. If it receives this specific error, the iOS SDK will automatically retry the operation with the full query document without you having to do anything.

To use APQs with the iOS SDK:

  • When generating your code, pass a local path for output for the --operationIdsPath (or pass a file URL to the operationIDsURL on ApolloCodegenOptions if using Swift Scripting).

    This will generate a document with all your operations, but more importantly it will cause operation identifiers to be generated with your code.

  • When creating your ApolloClient, make sure to manually instantiate your RequestChainNetworkTransport and set autoPersistQueries.

    This will cause the RequestChainNetworkTransport to actively look for the "Oh no, I don't have this hash!" error from the server.

By default, retries of queries will use POST. If for some reason (for example, your queries are hitting a CDN that has considerably better performance with GET), you need to use a GET for the 2nd try of a query, make sure to set the useGETForPersistedQueryRetry option to true. Most users will want to leave this option as false.

NOTE: APQs are not supported over Websockets at this time. If you're interested in this feature, please open a PR!

Feedback

Edit on GitHub

Forums