Core Data records are represented by the NSManagedObject class, a key class of the framework. In this episode, you learn how to create a managed object, what classes are involved, and how a managed object is saved to a persistent store.

What Is a Managed Object

At first glance, NSManagedObject instances may look like glorified dictionaries. All they seem to do is manage a collection of key-value pairs. It's true that the NSManagedObject class is a generic class, but it implements the fundamental behavior required for Core Data model objects. Let me explain what that means.

Creating a Managed Object

Open ViewController.swift and add an import statement for the Core Data framework. In the viewDidLoad() method, we're going to create a managed object to learn more about the inner workings of the NSManagedObject class.

ViewController.swift

import CoreData

To create an instance of the NSManagedObject class, we need two ingredients:

  • an entity description
  • and a managed object context

Entity Description

Every managed object has an entity description, an instance of the NSEntityDescription class. The entity description is accessible through the entity property of the managed object.

Earlier in this series, you learned what an entity is and we created several entities in the data model. An instance of the NSEntityDescription class represents an entity of the data model. As the name of the class implies, an NSEntityDescription instance describes an entity.

The entity description refers to a specific entity in the data model and it knows about the attributes and relationships of that entity. Every managed object is associated with an entity description.

We can create an entity description by invoking a class method of the NSEntityDescription class, entity(forEntityName:in:). We pass this method the name of the entity and a managed object context.

ViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()

    if let entityDescription = NSEntityDescription.entity(forEntityName: "Note", in: coreDataManager.managedObjectContext) {

    }
}

Why do we need to pass it a managed object context? Remember that we rarely, if ever, directly access the managed object model of the Core Data stack. We use the managed object context as a proxy to access the managed object model. It's the managed object model that contains the information about the entities of the data model.

The managed object context acts as a proxy for the managed object model.

Notice that the class method of the NSEntityDescription class returns an optional. What is that about? Core Data needs to make sure that you can only create managed objects for entities that exist in the data model. If we pass entity(forEntityName:in:) a name of an entity that doesn't exist in the data model, the entity description can't be created and entity(forEntityName:in:) returns nil.

We should now have a valid entity description. Let's print the name and the properties of the entity description to the console.

ViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()

    if let entityDescription = NSEntityDescription.entity(forEntityName: "Note", in: coreDataManager.managedObjectContext) {
        print(entityDescription.name ?? "No Name")
        print(entityDescription.properties)
    }
}

Run the application and inspect the output in the console. Ouch. The application seems to have crashed.

The persistent store coordinator was unable to add the persistent store.

The persistent store coordinator was unable to add the persistent store. If you encounter the same crash, remove the application from the device or simulator and run the application again. We explore the reason of the crash later in this series. This is what the output in the console looks like.

Console

Note
[(<NSAttributeDescription: 0x6000000ed980>), name contents, isOptional 1, isTransient 0, entity Note, renamingIdentifier contents, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}, attributeType 700 , attributeValueClassName NSString, defaultValue (null), (<NSAttributeDescription: 0x6000000eda80>), name createdAt, isOptional 1, isTransient 0, entity Note, renamingIdentifier createdAt, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}, attributeType 900 , attributeValueClassName NSDate, defaultValue (null), (<NSAttributeDescription: 0x6000000ed700>), name title, isOptional 1, isTransient 0, entity Note, renamingIdentifier title, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}, attributeType 700 , attributeValueClassName NSString, defaultValue (null), (<NSAttributeDescription: 0x6000000ed900>), name updatedAt, isOptional 1, isTransient 0, entity Note, renamingIdentifier updatedAt, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}, attributeType 900 , attributeValueClassName NSDate, defaultValue (null), (<NSRelationshipDescription: 0x600000130ea0>), name category, isOptional 1, isTransient 0, entity Note, renamingIdentifier category, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}, destination entity Category, inverseRelationship notes, minCount 0, maxCount 1, isOrdered 0, deleteRule 1, (<NSRelationshipDescription: 0x600000130e00>), name tags, isOptional 1, isTransient 0, entity Note, renamingIdentifier tags, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}, destination entity Tag, inverseRelationship notes, minCount 0, maxCount 0, isOrdered 0, deleteRule 1]

That looks like a lot of gibberish. If you take a closer look, though, it makes more sense. The first line tells us that the name of the entity is Note. We can also see that the entity has four attributes and two relationships.

Managed Object Context

To create a managed object, we also need a managed object context. What? Why do we need another reference to a managed object context? We already referenced a managed object context to create the entity description. Remember?

A managed object is always associated with a managed object context. There are no exceptions to this rule. Remember that a managed object context manages a number of records or managed objects. As a developer, you primarily interact with managed objects and the managed object context they belong to.

Why is a managed object context important? Remember from earlier in this series that the persistent store coordinator bridges the gap between the persistent store and the managed object context. In the managed object context, records are created, updated, and deleted. Because the managed object context is unaware of the persistent store, it pushes its changes to the persistent store coordinator, which updates the persistent store.

The managed object context is unaware of the persistent store. It it pushes its changes to the persistent store coordinator.

Creating a Managed Object

With an entity description and a managed object context, we now have the ingredients to create a managed object. We do this by invoking the init(entity:insertInto:) initializer. The result is a managed object for the Note entity.

ViewController.swift

// Initialize Managed Object
let note = NSManagedObject(entity: entityDescription, insertInto: coreDataManager.managedObjectContext)

The entity description and managed object context are both available as properties on the managed object. Add a print statement for the managed object, run the application again, and inspect the output in the console.

ViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()

    if let entityDescription = NSEntityDescription.entity(forEntityName: "Note", in: coreDataManager.managedObjectContext) {
        print(entityDescription.name ?? "No Name")
        print(entityDescription.properties)

        // Initialize Managed Object
        let note = NSManagedObject(entity: entityDescription, insertInto: coreDataManager.managedObjectContext)

        print(note)
    }
}
<Note: 0x6080000954a0> (entity: Note; id: 0x608000221440 <x-coredata:///Note/tE5C54D25-1766-4940-A0E5-F9361FA778052> ; data: {
    category = nil;
    contents = nil;
    createdAt = nil;
    tags =     (
    );
    title = nil;
    updatedAt = nil;
})

The output shows us that the managed object we created doesn't have values for any of its attributes or relationships. It also tells us that no tag or category records are associated with the note record.

Working With a Managed Object

Before we can save the managed object to the persistent store, we need to set the required properties of the managed object. This means we need to set the title, createdAt, and updatedAt attributes.

This is easy. To set a value, we invoke setValue(_:forKey:) and we pass in the value for the property and the name of the property.

ViewController.swift

// Initialize Managed Object
let note = NSManagedObject(entity: entityDescription, insertInto: coreDataManager.managedObjectContext)

// Configure Managed Object
note.setValue("My First Note", forKey: "title")
note.setValue(NSDate(), forKey: "createdAt")
note.setValue(NSDate(), forKey: "updatedAt")

If we run the application again and print the value of note to the console, we see that the managed object contains the data we set.

<Note: 0x61800009f9a0> (entity: Note; id: 0x61800003afa0 <x-coredata:///Note/t6B25F688-928B-4DDF-BE67-564B8FBE0DCB2> ; data: {
    category = nil;
    contents = nil;
    createdAt = "2017-07-05 12:25:59 +0000";
    tags =     (
    );
    title = "My First Note";
    updatedAt = "2017-07-05 12:25:59 +0000";
})

Saving the Managed Object Context

We've successfully created a managed object, a note record, and inserted it into a managed object context. At the moment, the managed object only lives in the managed object context it was inserted into. The persistent store isn't aware of the managed object we created.

To push the managed object to the persistent store, we need to save the managed object context. Remember that a managed object context is a workspace that allows us to work with managed objects. Any changes we make to the managed object are only pushed to the persistent store if we save the managed object context the managed object belongs to. We do this by invoking save() on the managed object context.

ViewController.swift

// Initialize Managed Object
let note = NSManagedObject(entity: entityDescription, insertInto: coreDataManager.managedObjectContext)

// Configure Managed Object
note.setValue("My First Note", forKey: "title")
note.setValue(NSDate(), forKey: "createdAt")
note.setValue(NSDate(), forKey: "updatedAt")

print(note)

do {
    try coreDataManager.managedObjectContext.save()
} catch {
    print("Unable to Save Managed Object Context")
    print("\(error), \(error.localizedDescription)")
}

Because save() is a throwing method, we wrap it in a do-catch statement. Any errors are handled in the catch clause.

We can inspect the persistent store to verify that the save operation was successful. I use SimPholders to make this easier. It allows me to quickly inspect the sandbox of any application installed in the simulator.

Inspecting the Application's Container

As you can see, the Documents directory contains a SQLite database and, if we open it, we can see that the note is saved to the database. The structure of the database isn't something we need to worry about. That's the task of Core Data. You should never directly interact with the persistent store of a Core Data application.

Inspecting the Application's Persistent Store

Even though we only created a note record in this episode, we learned a lot about how Core Data works under the hood. Knowing this is important for debugging problems you encounter along the way. And believe me, you will run into problems at some point. If you understand the fundamentals of the framework, you're in a much better position to solve any issues that arise. In the next episode, we continue working with managed objects.