Core Data and Swift: Subclassing NSManagedObject

1. Introduction

Earlier in this series, we created Done, a simple application to learn more about the NSFetchedResultsController class. In that project, we used key value coding (KVC) and key value observing (KVO) to create and update records. This works fine, but from the moment your project has any kind of complexity, you'll quickly run into issues. Not only is the KVC syntax verbose, valueForKey(_:) and setValue(_:forKey:), it may also introduce errors that are the result of typos. The following code snippet illustrates this problem well.

Each statement in the above code snippet returns a different result. In fact, every statement will result in an exception apart from the third statement, which uses the correct key as specified in the data model.

The above problem is easily solved by using string constants, but that's not the point I'm trying to make. Key value coding is great, but it is verbose and difficult to read if you're used to Swift's dot syntax. To make working with NSManagedObject instances easier, it's better to create an NSManagedObject subclass for each entity of the data model and that's what you'll learn in this article.

2. Subclassing NSManagedObject

To save some time, we're going to revisit Done, the application we created earlier in this series. Download it from GitHub and open it in Xcode.

Creating an NSManagedObject subclass is very easy. While it's possible to manually create an NSManagedObject subclass for an entity, it's easier to let Xcode do the work for you.

Open the data model of the project, Done.xcdatamodeld, and select the Item entity. Select New > File... from Xcode's File menu, choose the NSManagedObject subclass template from the Core Data section, and click Next.

Creating a NSManagedObject Subclass

Check the checkbox of the correct data model, Done, from the list of data models and click Next.

Selecting the Data Model

In the next step, you are asked to select the entities for which you want to create an NSManagedObject subclass. Check the checkbox of the Item entity and click Next.

Selecting the Entities

Choose a location to store the class files of the NSManagedObject subclass and make sure that Use scalar properties for primitive data types is checked. When working with Objective-C, this option is an important consideration. But for Swift, it's best to check it as it makes your code more readable and less complex. Don't forget to set Language to Swift and click Create to create the NSManagedObject subclass for the Item entity.

Configuring The NSManagedObject Subclass

3. NSManagedObject Anatomy

Navigate to the files Xcode created for you and take a look at their contents. Xcode should have create two files for you:

  • Item.swift
  • Item+CoreDataProperties.swift

If you're using an earlier version of Xcode, then Xcode may have created only one file. I recommend using Xcode 7—preferably Xcode 7.1 or higher—to follow along. The contents of Item.swift should be pretty easy to understand.

As you can see, Xcode has created a class for us, Item, that inherits from NSManagedObject. The comment Xcode has added to the class implementation is important. If you wish to add functionality to the class, you should add it here. Let's now take a look at the contents of Item+CoreDataProperties.swift.

There are a number of important details and a few new concepts. The first thing to notice is that this file defines an extension on the Item class. The extension declares three properties that correspond to the attributes of the Item entity, which we defined in the data model. The @NSManaged attribute is similar to the @dynamic attribute in Objective-C. The @NSManaged attribute tells the compiler that the storage and implementation of these properties will be provided at runtime. While this may sound great, Apple's documentation clearly states that @NSManaged should only be used in the context of Core Data.

If this sounds a bit confusing, then remember that @NSManaged is required for Core Data to do its work and the @NSManaged attribute should only be used for NSManagedObject subclasses.

What happens if you modify an entity and generate the files for the entity's NSManagedObject subclass again? That's a good question and Xcode warns you about this scenario. At the top of Item+CoreDataProperties.swift, below the copyright statement, Xcode has added the a comment for clarification.

Whenever you generate the files for an entity, Xcode will only replace the files of the extension. In other words, if you add an attribute to the Item entity and generate the files for the NSManagedObject subclass, Xcode replaces Item+CoreDataProperties.swift, leaving Item.swift untouched. That's why Xcode tells you to add functionality in Item.swift. This is very important to keep in mind.

The types of the properties may be a bit surprising. The name property is of type NSString?, because the attribute is marked as optional in the data model. The createdAt property is of type NSTimeInterval, because we checked the checkbox Use scalar properties for primitive data types. The same is true for the done property, which is of type Bool.

If we hadn't checked the checkbox, the createdAt property would be of type NSDate? and the done property would be of type NSNumber?. While it may be easier to use NSDate instances, it's certainly less practical to work with NSNumber objects if all you want is get and set boolean values.

4. Updating the Project

With the Item class ready to use, it's time to update the project by replacing any occurrences of valueForKey(_:) and setValue(_:forKey:).

ViewController

Open the implementation file of the ViewController class, navigate to the configureCell(_:atIndexPath:), and update the implementation as shown below.

We've made four changes. We first changed the type of the record variable to Item. The objectAtIndexPath(_:) method of the NSFetchedResultsController class returns an AnyObject instance. However, because we know the fetched results controller returns an Item instance we force downcast the result using the as! operator.

We also substitute the valueForKey(_:) calls. Instead, we use the properties of the record object, name and done. Thanks to Swift's dot syntax, the result is very legible.

To update the record, we no longer call setValue(_:forKey:). Instead, we use the dot syntax to set the record's done property. I'm sure you agree that this is much more elegant than using straight key value coding. We also don't need optional binding for the done property since the done property is of type Bool.

Remember that KVC and KVO remain an integral part of Core Data. Core Data uses valueForKey(_:) and setValue(_:forKey:) under the hood to get the job done.

AddToDoViewController

We also need to make a few changes in the AddToDoViewController class. In the save(_:) method, we first need to update the initialization of the NSManagedObject instance. Instead of initializing an NSManagedObject instance, we create a Item instance.

To populate the record, we use the dot syntax instead of the setValue(_:forKey:) method as shown below.

UpdateToDoViewController

The last class we need to update is the UpdateToDoViewController class. Let's start by changing the type of the record property to Item!.

This change will result in a warning in the ViewController class. To see what's wrong, open ViewController.swift and navigate to the prepareForSegue(_:sender:) method.

We ask the fetched results controller for the record at the selected index path. The type of the record variable is NSManagedObject, but the UpdateToDoViewController class expects an Item instance. The solution is very simple as you can see below.

Head back to the UpdateToDoViewController class and update the viewDidLoad() method as shown below.

We also need to update the save(_:) method, in which we replace setValue(_:forKey:) with the dot syntax.

Build the project and run the application in the simulator to see if everything is still working as expected.

5. Relationships

The current data model doesn't contain any relationships, but let's add a few to see how an NSManagedObject subclass with relationships looks like. As we've seen in the previous article of this series, we first need to create a new version of the data model. Select the data model in the Project Navigator and choose Add Model Version... from the Editor menu. Set Version name to Done 2 and base the model on the current data model, Done. Click Finish to create the new data model version.

Open Done 2.xcdatamodel, create a new entity named User, and add an attribute name of type String. Add a relationship items and set the destination to Item. Leave the inverse relationship blank for now. With the items relationship selected, open the Data Model Inspector on the right and set the relationship type to To Many. A user can have more than one item associated with them.

Adding User Entity

Select the Item entity, create a relationship user, and set the destination to User. Set the inverse relationship to items. This will automatically set the inverse relationship of the items relationship of the User entity. Note that the user relationship is not optional.

Updating Item Entity

Before we create the NSManagedObject subclasses for both entities, we need to tell the data model which data model version it should use. Select Done.xcdatamodeld, open the File Inspector on the right, and set the current model version to Done 2. When you do this, double-check that you've selected Done.xcdatamodeld, not Done.xcdatamodel.

Select New > File... from the File menu and choose the NSManagedObject subclass template from the Core Data section. From the list of data models, select Done 2.

Selecting Data Model Version

Select both entities from the list of entities. Because we've modified the Item entity, we need to regenerate the extension for the corresponding NSManagedObject subclass.

Selecting Entities

Item

Let's first take a look at the changes of the newly generated Item class. As you can see below, the extension on the Item class (Item+CoreDataProperties.swift) contains one additional property, user of type User?.

This is what a To One relationship looks like in an NSManagedObject subclass. Xcode is smart enough to infer that the type of the user property is User?, the NSManagedObject subclass we created a moment ago. Even though the user relationship is marked as not optional in the data model, the type of the user property is User?. It's not clear whether this is intentional or a bug in Xcode.

The implementation of the Item class (Item.swift) hasn't been updated by Xcode. Remember that Xcode doesn't touch this file if it already exists.

User

With what we've learned so far, the User class is easy to understand. Take a look at the extension of the User class (User+CoreDataProperties.swift). The first thing you'll notice is that the type of the items property is NSSet?. This shouldn't be a surprise, because we already knew that Core Data uses sets, instances of the NSSet class, for storing the members of a To Many relationship. Because the relationship is marked as optional in the data model, the type is NSSet?.

That's how easy it is to work with relationships in NSManagedObject subclasses.

6. Migrations

If you build the project and run the application in the simulator, you'll notice that the application crashes. The output in Xcode's console tells us that the data model used to open the persistent store is not compatible with the one that was used to create it. The reason of this problem is explained in detail in the previous article of this series. If you want to learn more about migrations and how to safely modify the data model, then I suggest you read that article.

Conclusion

Subclassing NSManagedObject is very common when working with Core Data. Not only does it add type safety, it also makes working with relationships much easier.

In the next installment of this series, we take a closer look at Core Data and concurrency. Concurrency is a tricky concept in almost any programming language, but knowing which pitfalls to avoid, makes it much less scary.

Tags:

Comments

Related Articles