“I’m developing an app and I’m not sure which data base to use (Firebase or CloudKit)”

This is wrong on multiple levels. Choosing the word “database” would prematurely confine the mind of the many options that are available for saving data. Naming technologies — “Firebase or CloudKit” – without a thorough understanding of the requirements is like picking a screwdriver when there’s a blackout. You need to understand what the problem is and what the app’s requirements are before making technology decisions. Similarly you need to replace the word “database” with “persistence” when considering options for saving data in an iOS app. This is mainly due to “database” being a loaded word, often imply a collection of tables containing rows of data. Similarly you shouldn’t prematurely narrow your decision on which cloud service to use.

Choosing tools without understanding the requirements, constraints, and alternatives is setting yourself for failure.

First let’s reveal the proverbial elephant in the room. The right choice today may not be the right choice tomorrow. Your app’s scope could have increase, hence the set of requirements have grown. Likewise your constraints could have changed. The app could be getting traction and you started to have funding that you can use to support your users better. Similarly technologies could improve, providing additional data persistence options that were not available before.

On the flip-side, choosing a persistence technology for tomorrow may hinder its implementation today. You could be writing much more code than required. You could be struggling to integrate foreign frameworks from differing sources. There could be edge-case issues that wasn’t discovered before, which is often the case for new or niche technologies. All of these are over-engineering, which is another case of premature optimization.

What if you can confidently decide what persistence to use for your iOS app? What if you have the tools to make an informed decision for today and for tomorrow?

The key is to engineer for today but design for growth. With persistence libraries this mean architecting the application in such a way that you can swap implementations or add new ones when a strong need arises. This is a common software engineering problem which is already solved by the Data Access Object (DAO) design pattern. This pattern is used by many enterprise software vendors to allow for interchangeable database implementations, hence the software may use a database that is preferred by the customer without the vendor needing to change the app too much.

When it comes to writing applications for rich platforms like Apple’s operating system, the choice of frameworks becomes even more simple: Always stick to the built-in technologies unless you have a strong reason to choose a third-party alternative. The first-party solution would have a better chance to being maintained and supported in subsequent operating system updates. It also forms a baseline in which the community builds upon. Selecting the built-in tools is even more important when you’re just starting out in the platform: when interviewing for a junior position, you won’t get high points for knowing a 3rd party alternative without being aware of the 1st party option as well. Only choose a 3rd party technology when there is no built-in alternative or it provides “must-have” features that’s not available in the built-in one.

The first-party options for data persistence in iOS and other Apple’s operating systems is already quite rich:

The Options

Property List

This is the preferred way to persist simple structured data for applications in Apple’s operating systems. There are built-in facilities to store objects into property lists as well as load them back. On-disk they are typically either XML files having a .plist extension or its binary equivalent. This file format is widely used in the operating system. Every app has an Info.plist file containing metadata about it. The User Defaults system is really a collection of property list files located in well-known places. The file format is widely used and documented, hence it won’t go away any time soon.

Adopting this is also quite simple. Confirm to the NSCoding protocol in your model objects and implement its methods to serialize and de-serialize the each property that you need persisted. Simple types such as numbers and strings can be encoded directly. Other object types can be encoded if it also confirms to NSCoding .

Unlike JSON, property lists also encode data types in the serialized form – including class names. This enables you to use polymorphism inside your persisted data. For example in a drawing application you can just serialize a property containing an array of shapes and let individual subclasses write out their own special properties into the serialized form.

File Package

Also known as a bundle, a file package is really a directory being shown to the user as if it were a single file. These directories have a file-name extension and a corresponding declaration which identifies the extension as a bundle. These are typically used for document-types data — where the user selects a document for editing and then close it when he/she is done.

Instead of inventing your own container format, you just use a file package and have the various components as files inside that directory. Another benefit of using file bundles is that it helps reduces the bandwidth needed to upload documents into the cloud. When your document is placed in iCloud Drive or other cloud providers, they won’t need to understand your container structure to synchronize the local copy of your document into the cloud. Instead it jus sees the component files and synchronize them as such. Therefore if the user only change one part of the document, only the changed file needs to be transported over the network, not the entire composite document.

Apple’s own Pages, Keynote and Numbers uses this file format, as well as some long-time 3rd party software on the platforms such as OmniGraffle and OmniFocus. These programs store data as file packages containing mostly property files. Except for external media such as JPG images or movies, which are stored verbatim inside the bundle. This allows efficient saving of documents since only the changed data needs to be written out to flash storage as well as efficient synchronization when stored inside a file provider on the cloud. For example, whenever you modify a bullet point in a Keynote presentation containing many images, only that slide’s property list needs to be synchronized and not the bundle.

How to use it is really simple. Choose a file extension and declare its type in the UTExportedTypeDeclarations section of your app’s Info.plist . Then you should use file wrapper APIs to coordinate reading and writing individual files inside the bundle. Using file wrappers would help when the file bundle is located in a cloud storage – this would coordinate uploading and synchronization to the file storage’s backend as well. When you use this correctly, your app will work well with the user’s chose cloud document provider — which may be iCloud Drive or a 3rd party one — without needing to program to their specific API. Finally use the document picker interface so that your users can open documents from any location – both inside the device or through a cloud provider.

SQLite

This is an open-source SQL database, not invented by Apple but provided as part of the official SDK, used extensively by the operating system and also Apple’s own applications. Hence you can be sure that this component would be supported in the platform for the foreseeable future. Unlike other SQL databases that is accessed via the network, SQL works in-process as a library linked to your application. Therefore SQLite saves its data as a local file.

The official interface to SQLite is a C-language API declared in the sqlite3.h header file, which you can find inside one of Xcode’s SDK header folders. Furthermore there are a number of open-source wrappers providing an object-oriented interface to it, some notable ones are FMDB for Objective-C and GRDB for Swift.

SQLite would be a good candidate for your persistence needs if you have a relatively small number of entities with minimum interdependencies between them. It would also be good if you have a large amount of read-only reference data – the kind that you generate at build time and include inside your application bundle as a read-only resource. However when there are many object types along with complex interdependencies between objects, the platform has a better framework that is designed just for that.

Core Data

When you need to maintain complex object graphs having many interdependent relationships, you should definitely consider Core Data. This is not just a database, but a rich structured data persistence library. Out of the box it maintains storage using either SQLite, property lists or just in-memory (useful for cases where you don’t need to persist the data, such as unit tests). You can also extend it by providing your own persistence that will work with the rest of the stack.

Core Data is used by the Apple itself, both in the operating system and in some of the built-in apps, one example is the Photos framework. Hence you can be sure that it would continue to be supported for a long time.

Keychain

The Keychain is a key-value store meant to store passwords and small bits of secret information. Small is key here as it is not designed to be performant for values bigger than a few bytes. Moreover the data store has pre-defined keys – you can’t define your own keys. The data is double-encrypted with the user’s passcode in addition to whatever file-system encryption that is already present. This is also one of the few places where data is retained when your app gets deleted.

iCloud Drive

This is really a folder that gets synchronized with the Cloud. What’s nice about iCloud Drive is that if you use Apple’s file coordination and supporting API to work well with iCloud Drive, your app would also work well with other vendors implementing file providers. This includes competitors such as Dropbox, Box, and OneDrive as well as document-managers such as DevonThink.

Implementing file coordination is simply notifying the operating system when you are writing to a document as well as being notified when someone else is writing to that document. This allows changes from the cloud to be reflected back into your application as well as providing your application an opportunity to resolve change conflicts. Including duplicating the entire document and keeping both changes.

Furthermore iCloud Drive allows the user to share documents with people that he/she knows who also have iCloud accounts. This is not just limited to iOS or macOS users since iCloud drive is also accessible on Windows and the web.

CloudKit

This is Apple’s backend for saving structured data in the cloud. There are two tiers of storages depending on whether it is a “private” or “public” database. The “private” database quota is backed by the user’s iCloud account and hence you don’t need to worry about paying for it. The “public” database quotas is counted against the developer account, but its free tier is very elastic and grows along with the number of active users of your app. Starting with 10GB of assets and 100 MB of database storage for development that grows to 25TB of assets and 250GB with 100K active users. In contrast, Firebase’s free tier stops at 1GB of storage and do not scale with the number of active users. Lastly there is a “shared” database which a user of your app creates and may choose be accessed only with people he/she knows — for quota purposes, this is counted along with the “public” database

The Conclusion

I’ve put together a flowchart to guide you in selecting which built-in iOS persistence technologies to use. The decision points lie mostly in how is the data structured and who is going to access it. Unstructured data such as images, sound, or movies almost always mean plain-old files or the more modern file package. Whereas structured data calls allows for a database which can organize the data better and provides provisions for retrieval of data based on its attributes.

Just remember to use the right tool for the job, which imply understanding the problem, fleshing out the requirements, and looking at the options available before making a choice. Also there’s nothing that’s preventing you to mix-and-match differing persistence technologies within one app, notably to support different use cases within the app – say for example, using a Core Data schema for local persistence and synchronize some parts of it through CloudKit to other devices and/or other users of your app.