NSManagedObject
is the class you interact with most when you work with Core Data. The class may appear as a glorified dictionary, but it is much more than that. In this tutorial, I show you three features of the NSManagedObject
class you may not know about.
Setting Dynamic Defaults
You probably know that you can set a default value for an attribute of an entity. Even though this works fine, the default value is static. You cannot assign a localized string to an attribute, for example. And setting the current date to an attribute isn’t possible either.
There is a solution to this problem, though. The NSManagedObject
class defines the awakeFromInsert()
method. This method is invoked when the managed object is inserted for the first time into a managed object context. The method is invoked once for each managed object. This means it is ideal for setting default values. Let me illustrate this with an example. This is what the User entity looks like.
We could manually set the createdAt
attribute of each User
instance when it is created. But a better solution is to automatically set the value when a new User
instance is inserted in its managed object context. This is easy to accomplish by overriding the awakeFromInsert()
method as shown below.
import CoreData
extension User {
override public func awakeFromInsert() {
setPrimitiveValue(NSDate(), forKey: "createdAt")
}
}
We create an extension for the User
class, an NSManagedObject
subclass, and override the awakeFromInsert()
method.
By using setPrimitiveValue(_:forKey:)
, we bypass KVC. This is necessary if you don’t want the undo manager to track the changes we make to the User
instance in the awakeFromInsert()
method.
On Save
The User entity also defines an updatedAt attribute. It would be nice if the value of this attribute is automatically updated every time the changes of a User
instance are pushed to the persistent store. We can accomplish this by overriding another instance method of the NSManagedObject
class, willSave()
.
As the name suggests, this method is invoked when the managed object is about to be saved, that is, when the changes of the managed object context the managed object belongs to are going to be pushed to the persistent store. Take a look at the implementation below.
import CoreData
extension User {
override public func willSave() {
if let updatedAt = updatedAt {
if updatedAt.timeIntervalSince(Date()) > 10.0 {
self.updatedAt = NSDate()
}
} else {
self.updatedAt = NSDate()
}
}
}
The implementation of the willSave()
method may look complicated, but let me explain what is happening. We first check whether updatedAt
has a value. If it doesn’t, we set updatedAt
to the current date.
If updatedAt
is not equal to nil
, we make sure the time difference between the value of updateAt
and the current date is bigger than ten seconds. Why is that important?
By setting the value of the updatedAt
property, we invoke another save operation because Core Data detects a change was made. This means that the willSave()
method is invoke again. This causes an infinite loop. Apple’s documentation emphasizes that this should be avoided at any cost.
The documentation also points out that there are two other options. We can bypass KVC by using setPrimitiveValue(_:forKey:)
as we did earlier in this tutorial. Alternatively, we can update the updatedAt
property of the User
instance by listening for a NSManagedObjectContextWillSaveNotification
notification. But know that these approaches also have side effects.
Entity
As of iOS 10 and macOS 10.12, it is possible to ask a NSManagedObject
subclass for its entity description through the entity()
class method. Before iOS 10 and macOS 10.12, developers implemented their own version of the entity()
class method. That is no longer necessary as you can see below.
User.entity()
Remember that it is also possible to ask a NSManagedObject
instance for its entity description. The NSManagedObject
class also defines an entity
property that returns an NSEntityDescription
instance.
user.entity
Core Data is a mature and rich framework. That is what makes it fun and challenging to work with. If you find yourself wresting with Core Data, chances are that there is a better solution to the problem you are trying to solve.