stratigrafia

SUBCLASSING NSMANAGEDOBJECT

3/21/20

On a recent CoreData app I was building, I subclassed my NSManagedObject, but ran into a couple of snags. Here’s what happened, how I fixed it, and how I could have avoided them to begin with.

Why subclass an NSManagedObject?

When using a NSManagedObject, you access their attributes by a key (a string). For example, suppose you have an NSManagedObject called restaurant, and you want its latitude attribute, you use the value(forKey:) method:

let theLatitude = restaurant.value(forKey: "latitude")

If you want to set the value of latitude, you use setValue(forKey:)

restaurant.setValue(44.74202, forKey: "latitude")

Providing a string as the key as in these examples ("latitude") is hazardous because any spelling mistake will create a run-time error. Only the first of these lines will work; all the rest will cause the app to crash.

let theLatitude = restaurant.value(forKey: "latitude")
let theLatitude = restaurant.value(forKey: "Latitude")
let theLatitude = restaurant.value(forKey: "lattitude")
let theLatitude = restaurant.value(forKey: "LATTITUDE")

The solution is to define the keys as a constants. There are several ways to do this, but the simplest is to have a line like this as a global constant (at the top of your file or in its own file):

let latitudeKey = "latitude"

From there, use this constant anywhere in your code. If you make a spelling mistake, the compiler will catch it—much better than an app crash.

restaurant.setValue(44.74202, forKey: latitudeKey)
let theLatitude = restaurant.value(forKey: latitudeKey)

You can also store keys as a struct or as an enum, and this is often preferable when using them as a global object.

struct Keys {
  static let name = "name"
  static let latitude = "latitude"
  static let longitude = "longitude"
}
 
enum Keys {
  static let name = "name"
  static let latitude = "latitude"
  static let longitude = "longitude"
}

Regardless of whether you store them in a struct or an enum, you access them the same way

let theLatitude = restaurant.value(forKey: Keys.latitude)

No matter how you store your keys, using setValue(forKey:) and value(forKey:) is verbose and tiring. The solution is to subclass NSManagedObject, which will let you access the attributes directly as properties.

restaurant.latitude = 44.74202
theLatitude = restaurant.latitude

Subclassing NSManagedObject—the right way

Apple’s documentation explains how to subclass the NSManagedObject correctly. First, select the .xcdatamodeld in the Navigator area (left column) of Xcode, then select the Entity (here, called Store). In the Inspector area on the right, go to Codegen and choose Manual/None. Next, go to the Editor menu, and choose Create NSManagedObject Subclass.

 

In the windows that follow, select the data model you wish to manage, then the entities you wish to manage, then where you want to store them in your project. For simple CoreData setups, there will be only one data model and one entity to select from. This will create two files.

The first of these is called ‘Store+CoreDataClass.swift’, with Store being whatever your entity is called. If you have any custom methods for your Entity, such as methods for displaying custom display strings or calculating values from your entity, add them here.

 

The second file is called ‘Store+CoreDataProperties.swift’, with Store being whatever your entity is called. This file should be left as is; it is what allows you to use your entity attributes as properties. The @NSManaged before each attribute tells the Swift compiler that the storage and implementation of a property will be provided at runtime. If you change your model and need to regenerate these files, the contents of this second file will be replaced, but your custom methods in the first file will be left as is.

Subclassing NSManagedObject—the wrong way

Skipping the step of setting Codegen to Manual/None will cause two errors when you attempt to compile your app, as I discovered.

The first type of error was a pair of errors about multiple commands producing two files:

 

This error is because the .xcdtamodeld file is no longer in the correct place. It is listed under Compile Sources, but it should now be under Copy Bundle Resources (see Positron’s answer edited by Reinhard Männer on Stack Overflow).

 

To move the file, first delete it from Compile Sources by selecting the .xcdatamodeld file and clicking the minus sign at the bottom of that list. Add it to Copy Bundle Resources by clicking the plus sign at the bottom of that list, choosing the file and adding it.

On the test app I built to replicate these errors, building the app at this point was successful. On my original app, I had second error.

The second error read:

filenames are used to distinguish private declarations with the same name

The fix was to select the .xcdatamodeld in the Navigator area of Xcode, select the Entity, then go to the Inspectors. Under Codegen, choose the Manual/None option, as I should have done at the start.

Codegen is the key

Codegen describes how Xcode will generate the code for your NSManagedObject subclasses, and there are three options:

• Class Definition is the default, and it is used when you first create an .xcdatamodeld; Xcode will use the .xcdatamodeld file to automatically create the NSManagedObject subclass. This creates the Entity+CoreDataClass.swift and Entity+CoreDataProperties.swift files, but they are stored in Derived Data, so it is not obvious that they have been generated. Do not edit these files: because they are automatically generated, any changes you make will be overwritten.

• Manual/None should be used if you plan to manually create your NSManagedObject subclasses. Again, be sure to set this before invoking the menu item Create NSManagedObject Subclass.

• Category/Extension is a hybrid of the the other two options. It will cause Xcode to automatically generate Entity+CoreDataProperties.swift, but you must generate Entity+CoreDataClass.swift yourself.

Apple has an extended discussion of these options, one that I wish I’d known about before I started subclassing!

 

Home