Core Data with Swift

Yo! It’s time for a boring refresher on Core Data.

This article is written for someone who used Core Data before, forgot most of it, and need to use it again now. I hope this article would{} give you a head start. Xcode 6.4 is used at the time of this writing.

What is Core Data?

Let's revisit what Core Data is, with definition from Core Data Programming Guide:

The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence.

What does this really mean?

Object life-cycle - This includes creating, reading and searching, updating, deleting, persisting, reversing changes, and other operations such as "faulting and uniquing" for your data model objects. Think about all the tasks related to "M" in the MVC architectural pattern.

Object graph - Your objects may have references to, i.e. linking to, one another, therefore forming a graph of objects.

That being said, most people, myself included, use Core Graph as a solution for storing data persistently, and that’s totally fine.

Core Data Stack

What's the Core Data Stack? Below is a quick summary for key components. Apple's notes are here.

  • NSManagedObject - The model objects.
  • NSManagedObjectContext - A scratch pad in which objects are created.
  • NSPersistentStoreCoordinator - Coordinate between context and persistent store.
  • NSPersistentStore - Allow model objects in a context to be saved to persistent storage. This talks to lower-level data store such as a SQLite database on the file system.

Each layer can have multiple instances, except for NSPersistentStoreCoordinator layer. There is only one instance of NSPersistentStoreCoordinator in one stack.

Using Core Data

Step 1 - Create Data Model File and Object classes

This is the first thing required in order to use Core Data in an existing project.

  • In Xcode go to Menu -> File -> New -> File -> iOS/Core Data -> Data Model.
  • Open the new Data Model file (.xcdatamodeld) and start creating Entities and add Attributes to each entity.
  • Once entities are defined, select Menu -> Editor -> "Create NSManagedObject Subclass”, Xcode will then create NSManagedObject subclasses for you. These are data objects that your app can create and pass around to serve your Model layer needs, from the MVC design perspective.
  • After creating the subclasses, you need to go back to Data Model file and update the “Class” info for each entity, to update PRODUCT_MODULE_NAME to match your real product module name.

If you are not sure about some options in the editor, try Core Data Model Editor Help from Apple.

For new projects, Xcode would have created this model file for you if you ticked "Use Core Data" during project creation. However Xcode wouldn't know what your data model is, so you still need to open the empty data model file, define your entities and create object classes.

Step 2 - Get your hands on NSManagedObjectContext

Your classes needs an instance of NSManagedObjectContext to perform Core Data operations. The easiest way to do this is to get the template code from a new project. Create a new project and tick the “Use Core Data” checkbox, you will find the following in AppDelegate.swift:

  • Property
    • applicationDocumentsDirectory
    • managedObjectModel
    • persistentStoreCoordinator
    • managedObjectContext
  • Function
    • saveContext

Just copy and paste all of them from the new project to AppDelegate of your existing project. Remember to change the data model file name and project name to that of the new project. And remember to add import CoreData to the top.

Once you have the code in AppDelegate, you can access the context from anywhere by

let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

You may think that it's not nice to dump such model layer code to AppDelegate. Well, you are not alone. We will touch on that later.

That's it! Your app can now create, read, update, delete and persist your data model objects using Core Data. Let's see how.

Common Tasks

Create New Objects

You can add new objects to a NSManagedObjectContext by:

// Create a new object.
let yourNewObject = NSEntityDescription.insertNewObjectForEntityForName("YourEntityName", inManagedObjectContext: managedObjectContext!) as! YourManagedObject

// Modify the new object.
yourNewObject.attribute1 = “Attribute 1"
yourNewObject.attribute2 = “Attribute 2"
...

Save Changes

Remember that managed object context is like a scratchpad. After creating objects in it, you must call its save() function in order to store its objects persistently.

var error : NSError?
// save() return YES if save operation succeeds.
if (managedObjectContext!.save(&error)) {
    // Log the error object, just in case there's something there.
    println(error?.localizedDescription)
}

Retrieve Persisted Objects

Use a NSFetchRequest to get the objects that you have created.

let fetchRequest = NSFetchRequest(entityName: “YourEntityName”)
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [YourModelObject] {
    // Do your work with fetchResults.
    ...
}

And what about...

Where should NSManagedObjectConext be?

People hold different opinions on this matter, and there is a StackOverflow post for exactly that. This is such an interesting topic, Apple provided an article that talks about Accessing the Core Data Stack too.

Below is an approach I found pretty neat. And it adheres to guidelines from the aforementioned Apple article too:

  1. Move Core Data stack out of AppDelegate to a new class. Let's say it is called DataStore.swift
  2. Create an instance of DataStore in AppDelegate.
  3. Pass this DataStore instance to view controllers or other classes that need to use it. You can pass it via init function or property setter.

This is a form of dependency injection, which is a nice way to reduce coupling in your classes.

Now you ask, why make it so complicated? When you create a new project with Core Data, Xcode already added the Core Data stack for you in the AppDelegate class. Naturally, you could simply access the NSManagedObjectContext directly like this:

(UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

This would work. However, deep down, you feel that something is not right. It's some kind of bad code smell.

After some rational, reflective thinking and staring at it for some time, you realised that maybe these are the reasons:

  1. You are accessing a variable of a Singleton class, and most likely from everywhere across the app, wherever Core Data is needed. This is against Low Coupling, High Cohesion principle.
  2. Since Core Data stack is written in AppDelegate, that most likely led yourself into writing everything related to Core Data right there as well. That contributed to a bloated AppDelegate class. Not nice.
  3. Because of (1) and (2), if you need to restructure your code in future, which you probably will, you may have to change a lot of code in a lot of classes. That sucks.

So, move your code out of AppDeleate! Do it now!

Can I have multiple NSManagedObjectConext in the app?

For example, let's say you have encapsulated CoreData logic into a DataStore class. It has NSManagedOjectContext that your view controller can use for data operations.

Let's say there are multiple screens that all need this DataStore class and its NSManagedObjectContext. What should you do? Can you create an instance of DataStore in each screen?

Not a good idea. You don't want to create multiple instnaces of NSManagedObjectContext in your app, unless there are special circumstances that require them. In particular, you'll get the error Illegal attempt to establish a relationship ... between objects in different contexts when you add relationships between objects.

One way around this is to access DataStore via a static shared instance. This way your app will only have one single instance of NSManagedObjectContext and avoid this problem.

References

Future writing topic - probably dependency injection and the momd file.

Core Data with Swift
Share this