In this tutorial, we take a look at the NSManagedObject
class, a key class of the Core Data framework. You learn how to create a managed object, what classes are involved, and how a managed object is saved to a persistent store.
Exploring NSManagedObject
At first glance, NSManagedObject
instances may appear to be glorified dictionaries. All they seem to do is manage a collection of key-value pairs. It is true that the NSManagedObject
class is a generic class, but it implements the fundamental behavior required for model objects in Core Data. Let me explain what that means.
Every NSManagedObject
instance has a number of properties that tell Core Data about the model object. The properties that interest us most are entity
and managedObjectContext
.
Entity Description
Every managed object has an entity description, an instance of the NSEntityDescription
class. The entity description is accessible through the entity
property.
Do you remember that we explored and defined entities in the tutorial about data models? An instance of the NSEntityDescription
class represents an entity of the data model.
The entity description refers to a specific entity of the data model and it knows about the attributes and relationships of that entity. Every managed object is associated with an entity description. No exceptions.
Managed Object Context
A managed object should always be 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? If you've read the tutorial about the Core Data stack, then you know that the persistent store coordinator bridges the gap between the persistent store and the managed object context. In the managed object context, records (managed objects) 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.
Setting Up the Project
Cloning Repository
It is time to start working with the NSManagedObject
class. Start by downloading or cloning the project we created for setting up the Core Data stack from scratch. You can find it on GitHub.
git clone https://github.com/bartjacobs/SettingUpTheCoreDataStackFromScratch.git
Open the project in Xcode and build it to make sure everything is working. Before we can create managed objects, we need to populate the data model of the project.
Populating the Data Model
Open the data model, Lists.xcdatamodeld, and add two entities, List and Item. List has two attributes and one relationship:
- name attribute of type String
- createdAt attribute of type Date
- items relationship of type To Many
Item has three attributes and one relationship:
- name attribute of type String
- createdAt attribute of type Date
- completed attribute of type Boolean
- list relationship of type To One
This means that a list can have zero or more items and each item belongs to one list. This could be the data model of a simple task manager. This is what the data model should look like in Xcode's data model editor:
Setting Up the Core Data Stack
Open AppDelegate.swift, add an import statement for the Core Data framework, and declare a property, coreDataManager
, of type CoreDataManager
.
import UIKit
import CoreData
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let coreDataManager = CoreDataManager(modelName: "Lists")
...
}
To make sure that the Core Data stack is set up correctly, update the implementation of the application(_:didFinishLaunchingWithOptions:)
method in AppDelegate.swift as shown below.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
print(coreDataManager.managedObjectContext)
return true
}
Build and run the application in the simulator or on a physical device and inspect the output in Xcode's console. You should see something like this.
<NSManagedObjectContext: 0x6000001db8a0>
Creating a Managed Object
To add a record to the persistent store, we need to create a managed object. We do this by invoking the designated initializer, init(entity:insertInto:)
. To create a managed object, we need:
- an entity description (
NSEntityDescription
) - a managed object context (
NSManagedObjectContext
)
Remember that the entity description tells Core Data what type of model object we would like to create. The managed object context we pass to the designated initializer is the one to which the managed object is added. That managed object context will manage the managed object.
How to Create an Entity Description
To create an entity description, we invoke a class method on the NSEntityDescription
class, entity(forEntityName:in:)
. We pass in the name of the entity and a managed object context. The entity name corresponds to the one defined in the data model.
Why does entity(forEntityName:in:)
require a NSManagedObjectContext
instance? As I mentioned earlier in this series, as a developer you access the Core Data stack primarily through a managed object context. You rarely interact with the persistent store coordinator or the managed object model. By passing a managed object context to entity(forEntityName:in:)
, you access the managed object model through the persistent store coordinator. Remember that every (parent) managed object context keeps a reference to a persistent store coordinator.
Why do we need to jump through so many hoops to create an entity description? Core Data needs to make sure that you can only create managed objects for entities that exist in the data model. Don't worry, though. Later in this series, I show you a more convenient technique to create managed objects.
In the example below, we create an entity description for the List entity. Note that entity(forEntityName:in:)
returns an optional because it is possible that the data model doesn't define an entity with that name.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let managedObjectContext = coreDataManager.managedObjectContext
// Create Entity Description
let entityDescription = NSEntityDescription.entity(forEntityName: "List", in: managedObjectContext)
if let entityDescription = entityDescription {
print(entityDescription.name ?? "No Name")
print(entityDescription.properties)
}
return true
}
The entity description has a range of properties and methods that give us information about the entity, including the name of the entity and its properties (attributes and relationships). Run the application and inspect the output in Xcode's console.
List
[(<NSAttributeDescription: 0x6100000f4380>), name createdAt, isOptional 1, isTransient 0, entity List, renamingIdentifier createdAt, validation predicates (
), warnings (
), versionHashModifier (null)
userInfo {
}, attributeType 900 , attributeValueClassName NSDate, defaultValue (null), (<NSAttributeDescription: 0x6100000f4080>), name name, isOptional 1, isTransient 0, entity List, renamingIdentifier name, validation predicates (
), warnings (
), versionHashModifier (null)
userInfo {
}, attributeType 700 , attributeValueClassName NSString, defaultValue (null), (<NSRelationshipDescription: 0x6100001224e0>), name items, isOptional 1, isTransient 0, entity List, renamingIdentifier items, validation predicates (
), warnings (
), versionHashModifier (null)
userInfo {
}, destination entity Item, inverseRelationship list, minCount 0, maxCount 0, isOrdered 0, deleteRule 1]
How to Create a Managed Object
To create a managed object with the entity description, we invoke init(entity:insertInto:)
, passing in the entity description and a managed object context.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let managedObjectContext = coreDataManager.managedObjectContext
// Create Entity Description
let entityDescription = NSEntityDescription.entity(forEntityName: "List", in: managedObjectContext)
if let entityDescription = entityDescription {
// Create Managed Object
let list = NSManagedObject(entity: entityDescription, insertInto: managedObjectContext)
print(list)
}
return true
}
The entity description and managed object context are both available as properties on the managed object. Run the application and inspect the output in the console.
CoreData: warning: Unable to load class named 'List' for entity 'List'. Class not found, using default NSManagedObject instead.
<NSManagedObject: 0x61800008d480> (entity: List; id: 0x61800002a760 <x-coredata:///List/t17932003-8626-407D-BA6C-B1ED92684DBE2> ; data: {
createdAt = nil;
items = (
);
name = nil;
})
Don't worry about the Core Data warning for now. Later in this series, I show you how we can resolve this warning. The output in the console shows us that the managed object we created doesn't have values for the name
and createdAt
attributes. It also tells us that no item records are associated with the list record. That is something we take care of later.
How to Save a Managed Object Context
We've successfully created a managed object, a list record, and inserted it into a managed object context. The managed object currently only lives in the managed object context it was created in. 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 in the managed object context are only propagated to the persistent store once we have successfully saved the managed object context.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let managedObjectContext = coreDataManager.managedObjectContext
// Create Entity Description
let entityDescription = NSEntityDescription.entity(forEntityName: "List", in: managedObjectContext)
if let entityDescription = entityDescription {
// Create Managed Object
let list = NSManagedObject(entity: entityDescription, insertInto: managedObjectContext)
print(list)
do {
// Save Changes
try managedObjectContext.save()
} catch {
// Error Handling
}
}
return true
}
Because the save()
method is a throwing method, we wrap it in a do-catch
statement. Any errors are handled in the catch
clause.
Questions? Leave them in the comments below or reach out to me on Twitter. You can download the source files of the tutorial from GitHub.
Even though we only created a list record in this tutorial, 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 are in a much better position to solve any issues that arise.
Now that you know what Core Data is and how the Core Data stack is set up, it's time to write some code. If you're serious about Core Data, check out Mastering Core Data With Swift. We build an application that is powered by Core Data and you learn everything you need to know to use Core Data in your own projects.