Getting started with Core Data using Swift 4

Posted on Posted in iOS

1. Overview


Core Data is an efficient framework for object graph management and persistence. It provides ready to use solutions which speed up implementation and solves well-known problems like tracking changes and migrations. It may become very handy if you need to store & process a lot of data.

People claim that learning curve is steep, but actually whole Model graph can be created using a simple visual editor and all CRUD operations are one/two-liners. I would say this reputation is caused mostly by lack of up-to-date tutorials. When I was learning this framework, it took me a while to find all basic information, because they are spread across many articles. That’s why I decided to gather here all you need to start.

It is also worth to mention main advantages of Core Data framework:

  • 3 ways to persist data: SQLite, binary and in memory (+xml on macOS).
  • Tracking changes in objects.
  • Very efficient and highly optimized.
  • Undo/redo mechanism.
  • NSFetchedResultsController (read more) which can be easily used to display data in UITableView and automatically:
    • track changes in objects,
    • group & display data in sections,
    • cache fetched results.
  • Support for background processing and conflict resolving (read more).
  • Support for lightweight (automatic) and heavyweight (manual) migrations.
  • Built-in editor for designing data model.

Unfortunately Core Data has no built-in encryption. If you want encrypted persistence, you need to create a custom store or implement custom transformation for selected attributes. There are also some 3rd party libraries. If you are looking for alternative solution read chapter “9. Core Data vs Realm”.

2. Create Data Model


To start working with Core Data we need to create a Data Model – a file containing definition of our entities. In order to do that, just press CMD+N and select Data Model template.

New Data Model

Now when we have our data model created, we can design entities using built-in editor.

Designer

To set up your first entity follow the steps below:

  1. Add a new entity and name it User.
  2. Add new attributes: firstName, lastName and username.
  3. Set type for each attribute.
  4. Optionally you can use graph editor.

Build your project and Xcode will automatically generate User class.

3. Create many-to-many relationship


Many-to-many relationship

Let’s first create another entity – Book. To define many-to-many relationship between User and Book follow the steps below:

  1. Add a new relationship for User.
  2. Set destination entity to Book.
  3. Set relationship type to To Many.
  4. Create a similar relationship for Book.
    Once both relationships are created, you will be able to set inverse entity to ensure a proper change tracking.

4. Strongly typed relationships


By default Xcode will generate relationship using properties of type NSSet. Casting each time retrieved objects doesn’t seem to be the best solution. Fortunately, it can be fixed by creating a custom model. In order to do that:

  1. Select User entity and set manual code generation.
  2. Open Editor menu and select Create NSManagedObject Subclass....
  3. In generated extension change NSSet to Set<Book>.
  4. Clean & build project.

Create subclass

5. Core Data Stack


Before requesting any data we need to first initialize Core Data Stack which includes:

  • NSManagedObjectModel – object describing schema of designed Data Model.
  • NSManagedObjectContext – monitors changes in entities and store. Interface for CRUD operations.
  • NSPersistentStoreCoordinator – saves and loads data from store. Associates persistent stores with a model.
  • NSPersistentContainer – initializes and sets up context, model and coordinator.

Initialization

  1. Create an instance of NSPersistentContainer for designed Data Model.
  2. Invoke initalization by calling loadPersistentStores method.
  3. Check if operation is finished with success.

In memory persistence

If you prefer to use in memory store instead of default SQLite, you can do that by simply adding these lines at the beginning of initializeStack() method.

6. Create, Read, Update, Delete (CRUD)


Once the Core Data Stack is initialized we can start working with data. Each operation can be performed using NSManagedObjectContext which you can access from NSPersistentStoreContainer. First I will add context property to DataController to make the code less redundant.

After each set of operations you should invoke self.context.save() to persist your changes. For now I put save in each method, but you don’t have to do this immediately. Especially if you want to have a larger transaction or implement undo feature.

Create (Insert)

Read (Fetch)

By default Xcode inside each entity will generate fetchRequest class function which can be used to retrieve all objects. However you can also create your own NSFetchRequest with custom NSPredicate.

Update

This part is quite interesting, you can see here change tracking in action. To update entity you can just modify its property and NSManagedObjectContext will be able to persist it properly after calling context.save() without passing any reference to object.

Delete

Batch update or delete

Sometimes we need to perform a lot of operations. To speed it up there is NSBatchDeleteRequest and NSBatchUpdateRequest.

7. Transactions


Using SQL databases you have transactions. Using Core Data you can rely on NSManagedObjectContext and built-in UndoManager. There is a set of simple methods to manage current state:

  • save() – similar to commit – persists all changes and resets undo stack.
  • rollback() – discards all changes since last save().
  • undo() – reverts last change.
  • redo() – reverts last undo action.

8. Migrations


Lightweight migration (automatic)

This migration assumes a “happy” case. Usually when you add/remove attributes/entities nothing should break, therefore migration should happen automatically without issues. In this case you have to:

  1. Select your Data Model.
  2. Open Editor menu.
  3. Select Add Model version...
  4. Select in Navigator (left panel) your new version.
  5. Perform desired changes – add/remove attributes/entities.

Once the new version is ready, you can migrate your Data Model to the latest version:

Lightweight migration

  1. Select any version of Data Model.
  2. Select any Entity (just to get a focus).
  3. Select File inspector.
  4. Set desired Model version.

Now you need to migrate also your objects representing entities:

  • if you have custom subclasses you need to manually add/remove properties or regenerate files.
  • if you use built-in code generation go to Product -> Clean Build Folder and build it again.

That’s it. You should be able now to work with new Data Model.

Heavyweight migration

This migration is required when you need to perform custom mapping which can’t be handled automatically by Core Data. For example you may want to change attribute type from String to Integer 16 or divide value by 2.0.

Heavyweight migration is tricky and it’s very easy to make a mistake, so pay attention to details in all steps. In this example I will show migration of attribute bonusPoints:String to bonusPoints:Int16.

  1. Perform all steps from Lightweight migration.
  2. Create a new class CustomMigration which inherits from NSEntityMigrationPolicy.
  3. Implement methods which will be used to map attributes, each should be marked with @objc attribute.
  4. Make sure that each method uses proper types. For example if you have in Data Model attribute of type Integer 32, your method should accept parameter of type NSNumber.
  5. Press CMD + N and add new Mapping Model.

Now when we have implemented migration, we can attach it to Mapping Model.

Heavyweight migration

  1. Select created Mapping Model.
  2. Select Entity which requires custom mapping.
  3. In the right panel, set Custom Policy to CoreDataTest.CustomMigration, notice that it’s important to specify module here.
  4. Set expression which will use our CustomMigration.toInt16 function:

    Second parameter contains selector to our function. It’s quite tricky – notice With keyword. If you are not sure about it just print it in Xcode:
  5. Make sure one more time that you followed all steps for Lightweight migration, set latest Model version and migrated your objects. If everything is done correctly, your migration should happen during next application run.

9. Core Data vs Realm

Realm seems to be more convenient solution right now, however I think it’s worth to learn both frameworks. Below there are some advantages of RealmSwift.

  • has built-in encryption
  • faster than Core Data (benchmark)
  • uses less space
  • a little bit simpler interface
  • easy migrations
  • easy fetching – no need to use NSFetchRequest
  • easy to use in multithreaded environment
  • support for auto-updating results
  • built-in observable notifications which allow to update UI
  • cross-platform library
  • support for cloud sync (extra paid)
  • Realm Studio which you can use to browse and manage database. It updates live when you open database from simulator’s storage.
  • really nice documentation and big community

I prepared a demo project which has two targets with the same functionality. One is using Core Data and another RealmSwift, so you can easily compare usage of both: Core Data vs Realm repository.

10. What’s next?