Up until now, we've used NSManagedObject instances to represent and interact with records stored in the persistent store. This works fine, but the syntax is verbose, we lose autocompletion, and type safety is also an issue.

Remember from earlier in this series that the values of a managed object are accessed by invoking value(forKey:) and setValue(_:forKey:).

record.value(forKey: "firstName")
record.setValue("Bart", forKey: "firstName")

Because value(forKey:) returns an object of type Any?, we need to cast the result to the type we expect, using optional binding.

if let name = record.value(forKey: "name") as? String {
    print(name)
}

While we could improve these examples by replacing string literals with constants, there is an even better approach, subclassing NSManagedObject.

Generating Subclasses

Download or clone the project we created in the previous tutorial and open it in Xcode.

git clone https://github.com/bartjacobs/MigratingADataModelWithCoreData

Replace the implementation of application(_:didFinishLaunchingWithOptions:) with the updated implementation below.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let managedObjectContext = coreDataManager.managedObjectContext

    // Seed Persistent Store
    seedPersistentStoreWithManagedObjectContext(managedObjectContext)

    // Create Fetch Request
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "List")

    // Add Sort Descriptor
    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]

    do {
        let records = try managedObjectContext.fetch(fetchRequest)

        for record in records {
            print((record as AnyObject).value(forKey: "name") ?? "no name")
        }

    } catch {
        let fetchError = error as NSError
        print("\(fetchError), \(fetchError.userInfo)")
    }
    
    return true
}

We seed the persistent store with dummy data, fetch every list records from the persistent store, and print the name of each list record to the console. In this tutorial, I'd like to show you how we can better interact with Core Data by subclassing NSManagedObject.

Prior to Xcode 8, developers manually created a NSManagedObject subclass for each entity. This is no longer necessary. As of Xcode 8, there is a much cleaner solution. Revisit the data model, Lists 2.xcdatamodel, and select the List entity. Open the Data Model Inspector on the right and take a close look at the Class section. The Codegen field is the setting we are interested in. As of Xcode 8.1, this setting is set by default to Class Definition.

Subclassing NSManagedObject | Code Generation for Entities

The possible options are Manual/None, Class Definition, and Category/Extension. By setting it to Class Definition, a NSManagedObject subclass is automatically generated for the entity and stored in the DerivedData folder, not in the project itself. And that is a good thing.

Because the default setting is Class Definition, we can already access a class named List without making any changes. But Core Data adds even more magic. Let me show you what that magic looks like.

Autocompletion and Type Safety

Creating a NSManagedObject subclass for each entity has a number of benefits, including autocompletion, an elegant syntax, and type safety. Revisit AppDelegate.swift and take a look at the updated implementation of the seedPersistentStoreWithManagedObjectContext(_:) method.

private func seedPersistentStoreWithManagedObjectContext(_ managedObjectContext: NSManagedObjectContext) {
    guard !UserDefaults.standard.bool(forKey: didSeedPersistentStore) else { return }

    let listNames = ["Home", "Work", "Leisure"]

    for listName in listNames {
        // Create List
        if let list = createRecordForEntity("List", inManagedObjectContext: managedObjectContext) as? List {
            // Populate List
            list.name = listName
            list.createdAt = NSDate()

            // Add Items
            for i in 1...10 {
                // Create Item
                if let item = createRecordForEntity("Item", inManagedObjectContext: managedObjectContext) as? Item {
                    // Set Attributes
                    item.name = "Item \(i)"
                    item.createdAt = NSDate()
                    item.completed = (i % 3 == 0)

                    // Set List Relationship
                    item.list = list
                }
            }
        }
    }
    
    do {
        // Save Managed Object Context
        try managedObjectContext.save()

    } catch {
        print("Unable to save managed object context.")
    }

    // Update User Defaults
    UserDefaults.standard.set(true, forKey: didSeedPersistentStore)
}

If you take a close look at the method's implementation, you see that we cast the records to the NSManagedObject subclasses Xcode has generated for us. This means that we no longer need to access properties using value(forKey:) and setValue(_:forKey:). We can take advantage of autocompletion and we can also benefit from Swift's type safety.

if let list = createRecordForEntity("List", inManagedObjectContext: managedObjectContext) as? List {
    // Populate List
    list.name = listName
    list.createdAt = NSDate()
    
    ...
}

Relationships also benefit from NSManagedObject subclasses. Instead of using setValue(_:forKey:) to set the list record of an item record, we set the list property of the item record.

item.list = list

We need to make two changes in application(_:didFinishLaunchingWithOptions:). A NSManagedObject subclass makes it very easy to fetch the records for a particular entity. Every NSManagedObject subclass defines a fetchRequest() method that returns an instance of the NSFetchRequest class. Note that we specify the type of records the fetch request returns.

// Create Fetch Request
let fetchRequest: NSFetchRequest<List> = List.fetchRequest()

The result is that the fetch(_:) method of the NSManagedObjectContext class returns an array of List records instead of generic NSManagedObject instances. The benefit is that we can treat the record objects in the do clause of the do-catch statement as List instances.

do {
    let records = try managedObjectContext.fetch(fetchRequest)

    for record in records {
        print(record.name ?? "no name")
    }

} catch {
    ...
}

This is what the updated implementation of application(_:didFinishLaunchingWithOptions:) looks like.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let managedObjectContext = coreDataManager.managedObjectContext

    // Seed Persistent Store
    seedPersistentStoreWithManagedObjectContext(managedObjectContext)

    // Create Fetch Request
    let fetchRequest: NSFetchRequest<List> = List.fetchRequest()

    // Add Sort Descriptor
    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]

    do {
        let records = try managedObjectContext.fetch(fetchRequest)

        for record in records {
            print(record.name ?? "no name")
        }

    } catch {
        let fetchError = error as NSError
        print("\(fetchError), \(fetchError.userInfo)")
    }
    
    return true
}

Should You Use NSManagedObject Subclasses?

Nothing prevents you from working with NSManagedObject instances. However, I hope this tutorial has convinced you of the benefits of NSManagedObject subclasses. Since Xcode 8 adds the ability to automatically generate a NSManagedObject subclass for each entity, there are very few arguments not to use them. In the rest of this series, we use NSManagedObject subclasses to interact with Core Data model objects.

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.

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.