- Apollo iOS takes advantage of this type information to map responses to Swift types.
- The type models suffer from the same issue mentioned above: most requests will only contain a subset of the data defined in these types.
- With GraphQL, the equivalent approach would be to define a model type for every type defined in your schema.
- With the approach, there would be a Post type containing all fields returned for posts, for example, and JSON results from individual endpoints are parsed into instances of this type.
- All primitive GraphQL types have a natural mapping to a corresponding Swift type.
We recently released the first version of Apollo iOS, a strongly-typed GraphQL client for Swift apps. I’ve written about bringing GraphQL to iOS before, but in this post I’d like to go into more…
@ReactDOM: Mapping GraphQL types to Swift
#js #graphql #reactjs #swift #ios @martijnwalraven
Static type safety from your server to your mobile UI
We recently released the first version of Apollo iOS, a strongly-typed GraphQL client for Swift apps. I’ve written about bringing GraphQL to iOS before, but in this post I’d like to go into more detail on our mapping from GraphQL types to Swift, and explain why we chose this approach. I’ll also show how the Xcode integration included with Apollo iOS gives you a convenient way of working with Swift and GraphQL side by side, and how that fits really well with the workflow enabled by the typed code generation.
For a more high-level introduction to GraphQL and its use on iOS, you may also be interested in a talk I recently gave at the Berlin GraphQL meetup. (Thanks to the Graphcool team for hosting me!)
Underlying many of the benefits of GraphQL is the fact that it is strongly typed. Servers define a schema, a type system that precisely describes what data is available to clients. And clients define queries specifying the structure they want a response to have in terms of this type system. This means that even though responses are usually transported as weakly-typed JSON, result types are in fact fully predictable. For each field, we have the type information to know exactly what data to expect. The schema thus acts as a strongly typed and self-documenting contract between client and server.
, but also for schema-specific types, for which Swift types will be generated as part of the build process.
Both GraphQL and Swift have the notion of an enum type, for instance, so even though enum values are transported as strings in JSON, these values are treated as native enums in Swift. This gives you full type safety and autocompletion in Xcode. The generated Swift enum type even contains descriptions from the schema as comments, so they can be shown in Xcode’s Quick Help:
If you deserialize JSON as untyped dictionaries in Swift, you’ll have to access data by string keys and manually cast values to the right type. Both operations are potentially fallible, so every value you access will in effect be optional:
). Apollo iOS maps non-nullable GraphQL types to non-optional Swift types, and optionals are only used if the schema defines a field as nullable. This means you don’t have to deal with Swift optionals unless data is truly optional.
What’s in a model?
We’ve seen above how dealing with untyped data is a pain in a language like Swift. It also means losing the advantages of strong typing. For that reason, when interacting with a RESTful API, many developers choose to map network responses to model types to keep weak typing from spreading to their UI. In fact, there are now dozens of JSON libraries available for Swift that either make writing these mappings easier, or rely on code generation to generate the mappings for you.
type containing all fields returned for posts, for example, and JSON results from individual endpoints are parsed into instances of this type.
A big benefit of this over raw JSON is that these models are a lot more convenient to work with, and they also give you certain amount of type safety. Model properties are typed, so there is no need for casts. And using named properties instead of string keys means the compiler can warn you when you make a typo or try to access a field that does not exist on a specific model.
Even though this is a huge improvement over untyped data access, practical considerations often mean not all REST endpoints will return all fields for every resource, so you will likely end up with partially filled models. This means all model properties will have to be defined as optional, and you have no principled way of keeping track of what data is guaranteed to be there as a result of a particular request.
Type models vs. query models
With GraphQL, the equivalent approach would be to define a model type for every type defined in your schema. Query results could then be parsed into model instances that allow for type safe data access:
In contrast to REST however, GraphQL does have a principled way of keeping track of what data gets returned for a specific request, because queries always fully specify the structure of the response.
Query models allow you to carry this specificity over to Swift. The way this works is that Apollo iOS takes a GraphQL schema and a set of GraphQL query documents, and uses these to generate query-specific model types that only contain the fields defined in the query:
type with a subset of the fields from the GraphQL type.
uses a query class as a generic parameter, which allows the compiler to automatically infer the type of the data returned. Type inference in Swift makes this entirely unintrusive, but it gives you complete static type safety for your data access code, catching many mistakes at build-time that would otherwise only be detected at run-time.
Your Swift code will only be able to access a field if the corresponding query asks for it. Removing a field from a query will lead to compile-time errors for any code that accesses the field. This means you can be sure at build-time that the data you access on models will actually be fetched as part of the query that returns them:
Our Xcode integration makes the mapping even more seamless, because it allows you to conveniently work with Swift and GraphQL side by side.
. This way, you can keep data definitions and data access colocated.