Core Data and Apollo GraphQL

By employing a protocol-based approach to Core Data and implementing fragments in the Apollo iOS client, robust caching becomes a trivial process.

Core Data

Even with some recent improvements, Core Data has never been easy to use. I use a protocol-based approach to simplify repetitive tasks and make Core Data more extensible.

ManagedObject/ManagedObjectSupport

I start by defining a class named ManagedObject, a subclass of NSManagedObject, that will be the superclass of all other Core Data objects. At a minimum, this class contains an idString property that simplifies creation and retrieval of objects. ManagedObject is also a good place to define any properties and methods that should be common to all objects.

class ManagedObject: NSManagedObject {
  @NSManaged var idString: String
}

Additionally, I define a protocol named ManagedObjectSupport. This protocol has no requirements, but allows me to implement static methods for object creation/retrieval and fetched results controller construction.

protocol ManagedObjectSupport {}
extension ManagedObject: ManagedObjectSupport {}

extension ManagedObjectSupport where Self: ManagedObject {
  
  // Creates or retrieves an object with the specified id
  static func object(in context: NSManagedObjectContext, withId id: String) -> Self {
    let request = Self.fetchRequest() as! NSFetchRequest<Self>
    request.predicate = NSPredicate(format: "%K == %@",  #keyPath(idString), id)
    request.fetchLimit = 1
    request.returnsObjectsAsFaults = false

    switch (try? context.fetch(request))?.first {
    case .some(let object):
      return object

    case .none:
      let newObject = Self(context: context)
      newObject.idString = id
      return newObject
    }
  }

  // Returns an existing object with the specified id
  static func existingObject(in context: NSManagedObjectContext, withId id: String) -> Self? {
    let request = Self.fetchRequest() as! NSFetchRequest<Self>
    request.predicate = NSPredicate(format: "%K == %@", #keyPath(idString), id)
    request.fetchLimit = 1
    request.returnsObjectsAsFaults = false
    return (try? context.fetch(request))?.first
  }

  // Configure and return a fetched results controller
  static func fetchedResultsController(in context: NSManagedObjectContext,
                                       sortDescriptors: [NSSortDescriptor]? = nil,
                                       predicate: NSPredicate? = nil,
                                       sectionKeyPath: String? = nil) -> NSFetchedResultsController<Self> {
    let request = Self.fetchRequest() as! NSFetchRequest<Self>
    request.sortDescriptors = sortDescriptors
    request.predicate = predicate
    return NSFetchedResultsController(fetchRequest: request, managedObjectContext: context,
                                      sectionNameKeyPath: sectionKeyPath, cacheName: nil)
  }

I can now define and access a Person subclass using the methods in ManagedObjectSupport.

class Person: ManagedObject {
  // idString is inherited from the ManagedObject superclass
  
  @NSManaged var firstName: String?
  @NSManaged var lastName: String?
  @NSManaged var nickname: String?
}

let p = Person.object(in: <someContext>, withId: "abc")
let e = Person.existingObject(in: <someContext>, withId: "abc")
let f = Person.fetchedResultsController(in: <someContext>, sortDescriptors: <someDescriptors>)

Apollo GraphQL

When researching GraphQL options for iOS, Apollo’s client quickly rose to the top of my list. It is open-source, actively supported, and written entirely in Swift. The client contains tooling to generate type-safe Swift code from a GraphQL schema, but that code can sometimes be complicated to use. The use of GraphQL fragments has simplified the generated code and led to easy reuse of types.

GraphQL Fragments

Normally, we declare the returned data from GraphQL operations with individual fields from a type defined in the schema.

query Person($id: ID!) {
  person(id: $id) {
    id
    firstName
    lastName
    nickname
  }
}

query AllPersons() {
  allPersons {
    id
    firstName
    lastName
    nickname
  }
}

However, Apollo’s code-generation will scope the return fields to each operation, creating duplication and making it difficult to reuse these repeated structures. Fortunately, GraphQL fragments are the perfect solution to this problem (read more about fragments here). In nearly every case, I start by defining a fragment for the GraphQL type and use that fragment in any operation returns. Unifying the types in this way makes for greater ease of use and also reduces the size of Apollo’s generated code.

fragment PersonFragment on Person {
  id
  firstName
  lastName
  nickname
}

query Person($id: ID!) {
  person(id: $id) {
    ...PersonFragment
  }
}

query AllPersons() {
  allPersons {
    ...PersonFragment
  }
}

Putting it together

On their own, a protocol-based approach to Core Data and the use of GraphQL fragments are valuable tools. When used together, though, object creation from returned GraphQL data becomes trivial while retaining type safety.

FragmentUpdatable

I define a protocol named FragmentUpdatable, which ManagedObject subclasses use to specify what type of fragment to receive data from. This protocol is then extended to leverage the ManagedObjectSupport protocol for object creation and retrieval.

protocol FragmentUpdatable {
  associatedtype Fragment: GraphQLFragment & Identifiable
  func update(with fragment: Fragment)
}

extension FragmentUpdatable where Self: ManagedObject {
  static func object(in context: NSManagedObjectContext, withFragment fragment: Self.Fragment?) -> Self? {
    guard let fragment = fragment, let id = fragment.id as? String else { return nil }
    let object = Self.object(in: context, withId: id)
    object.update(with: fragment)
    return o
  }
}

Each ManagedObject subclass can conform to FragmentUpdatable to describe which fragment it accepts and how to map the data. Notice that the protocol requires the Fragment type to be Identifiable. Since PersonFragment already has an id property, an empty extension is all that is necessary.

extension PersonFragment: Identifiable {}

extension Person: FragmentUpdatable {
  typealias Fragment = PersonFragment

  func update(with fragment: Fragment) {
    self.firstName = fragment.firstName
    self.lastName = fragment.lastName
    self.nickname = fragment.nickname
  }
}

Now, after a GraphQL operation returns, I can create/retrieve/update in one line.

let person = Person.object(in: <someContext>, withFragment: <somePersonFragment>)

4 thoughts on “Core Data and Apollo GraphQL

  1. Hi Joel,
    I read your article with great interest because this seems to fit perfectly in my project. When I try to integrate it after your instructions Core Data comes up with following errors when the line

    let request = Self.fetchRequest() as! NSFetchRequest
    is executed by
    AwningLite.object(in: self.managedObjectContext, withFragment: $0!.fragments.allAwningFragment)

    2020-08-31 17:41:28.363857+0200 mx-ios[20376:10777290] [error] error: No NSEntityDescriptions in any model claim the NSManagedObject subclass ‘mx_ios.AwningLite’ so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
    CoreData: error: No NSEntityDescriptions in any model claim the NSManagedObject subclass ‘mx_ios.AwningLite’ so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
    2020-08-31 17:41:28.364096+0200 mx-ios[20376:10777290] [error] error: +[mx_ios.AwningLite entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
    CoreData: error: +[mx_ios.AwningLite entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass

    I do not explicitly define a .xcdatamodeId file for my model AwningLite.

    Do you have any idea what’s going wrong?

    Thank you very much for your help
    Kind regards
    Hendrik

    1. Hello Hendrik! Those errors are indeed the result of not defining entities in a data model. I should have been more detailed about setting up the entities. So, using your AwningLite class as an example…

      1. Add an .xcdatamodeld file in your project
      2. Add an `AwningLite` entity to the data model. In the entity inspector (on the right), set the class name to `AwningLite`, the module to `Current Product Module` and the codegen to `Manual/None’.
      3. At a bare minimum, the new entity should have a non-optional String attribute named `idString`. Add any other attributes you wish.
      4. Define your `AwningLite` class, which inherits from `ManagedObject` to access the functionality described in the post.

      I hope this helps get you moving in the right direction.

      Cheers,
      Joel

  2. Hi Joel,
    I have a high-level question. I’m not a dev.

    We use GraphQL on top of a PostgreSQL database on our server.
    We have a tiny iOS client (video library) using the Apollo Client which works well.

    We would like to download/store some of the information we query “forever” locally on the iOS client to be able to search locally for favourite films, info about the film makers, synopsis etc. even when there is no internet connection.

    Should we replicate our PostgreSQL schema in CoreData and use GraphQL/Apollo Client to query that. Or should we do what you article suggests.

    I hope my questions makes some kind of sense even if it plausibly is not strictly correct.

    All the Best!

    MJ

    1. Hello MJ,

      Core Data is not a database, though it is typically backed by one. It is a graph management system that allows for sophisticated fetching of objects. As such, I’m not sure how or why one would bridge Apollo queries into Core Data. Treating Core Data in this way would miss out on the fetching, filtering, and sorting capabilities that are its real strength.

      The approach I use is to define Core Data objects and relationships that reflect how I want to present information to the user. In many cases, this replicates the resources in an API, but I also frequently translate and combine resources to make them more idiomatic to iOS. I use the technique described to translate information received from Apollo operations into Core Data objects, effectively building a client-side cache (which can be used for offline access). This allows me to use Core Data’s fetching mechanism to retrieve, filter, sort, and present this information.

      I hope this helps.

      Joel

Leave a Reply

Your email address will not be published. Required fields are marked *