CoreData & CloudKit — (part 1)

Alejandro Barros Cuetos
5 min readJun 28, 2020

--

When designing a native iOS application one of the first decisions that should be made is which persistence strategy to use, the approach taken for it will greatly influence the rest of the application’s architecture. I don’t mean the actual technology to use, the particular technology, at least in medium or big size applications, will, ideally, be abstracted using some of the common patterns for it, allowing the strategy to be agnostic of the technology itself.

How to abstract the persistency layer is enough in itself for a complete series of articles, so I won’t be covering it here.

Introduction

For this exercise let’s assume the following persistence requirements:

  1. The data should be available in all of the user’s devices.
  2. The application will only be available in Apple platforms.
  3. Real time collaboration is not a requirement.
  4. The user should be able to use the application while being offline.

Some of the most common persistence strategies are: (Not an exhaustive list)

  • Online storage only, the application doesn’t stores any data, every piece of data required is fetched from a remote server.
  • Local storage only, the application holds all the data itself.
  • Real time synchronisation, data gets updated in real time in both, the device and the backend storage allowing real time collaboration.
  • Local storage with remote mirroring.

Given the requirements that we have, the last one, Local storage with remote mirroring looks like the perfect approach for us. For this persistence strategy, we need three different basic elements, a local storage system, a remote storage system and a mechanism to mirror and update both storages in a simple and effective way.

I won’t get into details of all the solutions out there that provide, among others, this strategy (there are many) but for reference these are some of the most popular ones:

As per the requirements, the application will only be available in Apple devices, so we’ll be using CoreData + CloudKit as it’s tightly integrated in Apple’s ecosystem and provides an easy API to integrate with our Swift application.

CloudKit is not free, although it provides an interesting free tier that grows with your users, so depending on your requirements, it might be quite cheap to use. Before committing yourself to use it, I’d advise you to review it’s pricing and features in https://developer.apple.com/icloud/cloudkit/ to make sure it fits your requirements and costs.

How it works

Oversimplifying it, the applications operate with their local CoreData store, and those operations are synchronised with the CloudKit container and CloudKit itself takes care of replicating those changes in all of the user’s devices.

Simplified diagram of the local and remote stores

There are substantial differences between CloudKit’s public and private databases, this article only covers using private databases.

Going into a bit more of detail, let’s see how the implementation handles the most common scenario, the user modifies or creates some piece of data in a device and how it gets replicated.

With the correct setup in place, operating in the data is like any other CoreData operation using the managed object context, once the operation has been persisted in the CoreData Store, the system will transform that Managed Object into a CloudKit Record and synchronise the operation with the CloudKit private database for the user. This transformation and synchronisation happens in a background thread, making the entire process completely transparent for your user and creating a nice user experience.

The store will keep trying to synchronise the operation with the CloudKit Container until it’s successful, to accomplish this, even when the user might be offline, we’ll need to implement `NSPersistentHistory` (in part 2 of this series). This seems a pretty normal and standard approach on how to synchronise local data with a remote storage, nothing surprising here, the most complex part would be the transformation between a Managed Object and a CloudKit Record, but, happily, this is done for us by the framework. Let’s dive into the interesting part of the process.

How the changes and data are mirrored across the user’s devices?, do we need to implement polling on application start? how do we get the new data if the user has the application opened in two devices?. No, it’s much more simpler than that, CloudKit uses Push Notifications to mirror the data.

Worth mentioning that if you’re using the public database, you’ll need to implement a polling policy.

CloudKit will send Push Notifications regularly to all the devices of that user which will silently trigger an update of the local CoreData Store.

The really nice feature of the framework, is that once you do the initial setup, albeit a bit cumbersome, all the complexity of synchronising different stores is managed by the system. With one exception, duplicates, we still need to handle duplicated records ourselves. One of the articles of this series, will go into detail on how to manage duplicate records.

Ok!, enough theory, the next article of the series will explain how to setup your environment and we’ll be creating our first model with lots of code.

This series will be composed of the following topics.

  • Concepts — This article.
  • Setup, CoreDataStack and first shared model— Coming soon.
  • Managing duplicates and big objects — Coming soon.
  • Integrating the Stack in a SwiftUI application — Coming soon.
  • Switching to a MVVM architecture for the application — Coming soon.
  • Final touches — Coming soon.

--

--

No responses yet