Home » Core Data

Core Data: Subclassing NSManagedObject

26 March 2009 11,210 views 7 Comments

Seasoned Objective-C developers are accustomed to certain ways of subclassing objects. While we definitely subclass objects less often than developers of other languages, it is still necessary in many cases.

When it comes to the NSManagedObject, the rules for subclassing are a bit different.

Do not implement -init

Apple recommends that we do not use any of the -init methods of the NSManagedObject. We can guess or test to discover why this is the case but I have found that anything we would want to do in -init is better served in one of two other methods.

For example, imagine that we want to pre-populate some defaults in an object so that they are never nil. However, when pre-populating, we would want to avoid overwriting any changes that the user has already set. Therefore we would need to check to see if the value is already there and if its changed. Easy right? The code would probably look something like

-(id)init
{
  if (![super init]) return nil;
  if (![self valueForKey:@"name"]) {
    [self setValue:@"untitled" forKey:@"name"];
  }
  return self;
}

But there is a problem with this. The data is not yet in our object so [self valueForKey:@"name"] will always return nil. Worse, when we call [self setValue:@"untitled" forKey:@"name"], the set will be overwritten when the data is “loaded” into the class.

So what is the trick? At what point can we check to make sure the data is in place? The answer is; it depends. If we are working with a brand new object then we want to override -awakeFromInsert and if we are dealing with an existing object we want to override -awakeFromFetch.

awakeFromInsert

The -awakeFromInsert method is called after the object has been fully realized and is just about to be handed back to the current run cycle. This means that any processing that Core Data was going to do with this object has already been done and it is ready for us to make changes to it.

This is the perfect insertion point to do anything else we want done to the object before it goes back to the normal program flow. For example, if we wanted to set the name value (although this can be done in the data model but it is a simple example), we would implement it as follows:

-(void)awakeFromInsert
{
  [super awakeFromInsert];
  [self setPrimitiveValue:@"untitled" forKey:@"name"];
}

Note that we do not need to check to see if the value is already nil because this method will only ever be called once in the entire lifecycle of this object. One entity, or row if you prefer to look at it like a database, will have this method called exactly once — ever.

I should also point out that I am using -setPrimitiveValue:forKey: in this example which I will discuss in greater depth below.

awakeFromFetch

As a companion to -awakeFromInsert, -awakeFromFetch is called every time that the object is realized from the persistent store. This means that every time the object is loaded into memory from disk, this method will be called just before it is handed back to the normal program flow.

Why is this useful? There are a number of reasons. If we have transient or calculated values in our subclass then this is a perfect opportunity to calculate or set those transient values before the user or the UI starts reading those values.

One thing that should not be attempted in the -awakeFromFetch method, however, is setting or altering relationships. The reason for this has to with the way that relationships are managed.

Normally when we set one side of a relationship, Core Data automatically updates the other side of the relationship. However, within the -awakeFromFetch method, the relationship observation is turned off. Therefore if we change a relationship within the -awakeFromFetch method then the other side will not get updated and our relationship will become inconsistent.

This does not stop us from accessing relationships and use them to calculate or set transient values, but setting them should be avoided.

Overriding the accessors

With the addition of properties, it is easier than ever to add concrete accessors to NSManagedObject subclasses. Prior to Leopard, if we wanted to be able to call [mySubclass name] instead of [mySubclass valueForKey:@"name"] we had to implement something like the following:

- (NSString*)name
{
  [self willAccessValueForKey:@"name"];
  NSString *value = [self primitiveValueForKey:@"name"];
  [self didAccessValueForKey:@"name"];
  return value;
}

Fortunately, we don’t have to do that anymore. Now we can just call [mySubclass name] directly without having to actually implement the accessor. However this will produce a warning in the compiler that can be annoying. To avoid this, Apple added the option to flag a property as dynamic. This tells the compiler that the method will be there at runtime and to not bother with warnings. Naturally if the method does not show up at runtime the application would crash. Fortunately Core Data takes care of the implementation for us.

To configure this for our example object, we would first need to declare the property in the header file:

@property (retain) NSString *name;

Then within the implementation file we would define it as dynamic:

@dynamic name;

And that is it. Core Data takes care of the rest for us.

However if we do want to implement one or both of the accessors ourselves then we need to do a little extra work to play by the rules.

willAccessValueForKey / didAccessValueForKey

Normally when we write accessors we do not need to be concerned with KVO (Key Value Observing). Normally the language handles that for us. However when we are dealing with NSManagedObject subclasses then the normal KVO rules do not apply. This is because Core Data turns automatic KVO off and implements it manually.

Therefore, to be proper citizens we need to notify KVO when we are accessing or changing a value. In the case of accessing a value, we would need to implement it like it was shown above:

- (NSString*)name
{
  [self willAccessValueForKey:@"name"];
  NSString *value = [self primitiveValueForKey:@"name"];
  [self didAccessValueForKey:@"name"];
  return value;
}

This notifies KVO that the value is being accessed.

willChangeValueForKey / didAccessValueForKey

Like the accessor notifications above, if we are going to implement our own setter accessor then we need to manage the KVO for that as well. As a counterpart to the getter accessor above, the setter would be as follows:

- (void)setName:(NSString*)name
{
  [self willChangeValueForKey:@"name"];
  [self setPrimitiveValue:name forKey:@"name"];
  [self didChangeValueForKey:@"name"];
}

In this example we are using the -willChangeValueForKey: and -didChangeValueForKey: methods which are fairly common in normal KVO usage. They provide a manual notification method to all of the observers of the property being changed.

primitive accessors

Those who are new to Core Data may be surprised by the use of -primitiveValueForKey: and -setPrimitiveValue:forKey:. These methods are used in place of the normal KVC methods -valueForKey: and -setValue:forKey that are used throughout Objective-C. The primary difference is that these methods do not trigger KVO notifications. This allows us to make many changes to a NSManagedObject subclass all at once and either forgo notification or delay the notifications until the entire object has been updated. This is particularly useful during large imports of data.

Do not use -dealloc

The last very important item to know about when subclassing NSManagedObject. Because objects can be turned into faults (structure remains but the data is unloaded from memory) we do not want to rely on the -dealloc method as it is quite possible it will never be called during the lifetime of our application.

Instead, when we need to release transient values we need to use either -willTurnIntoFault or -didTurnIntoFault depending on the situation. These methods are called during the fault process and allow us to remove observers or release transient values as needed.

Marcus Zarra

marcusMarcus S. Zarra is the owner of Zarra Studios LLC and the creator of seSales and iWeb Buddy as well as being a co-author of “Cocoa Is My Girlfriend”, a wildly popular blog covering all aspects of Cocoa development. Marcus has been developing software since the mid-1980s and has written software in all of the major technological fields. Marcus has been using Core Data since its original release in OS X 10.4 Tiger and has released numerous applications and papers covering all of the topics of Core Data.

7 Comments »

  • Subclassing NSManagedObject | Kuba Seniorita Blog and links said:

    [...] Zarra on The Mac Developer Network: “Seasoned Objective-C developers are accustomed to certain ways of subclassing objects. While we [...]

  • Subclassing NSManagedObject said:

    [...] Zarra on The Mac Developer Network: “Seasoned Objective-C developers are accustomed to certain ways of subclassing objects. While we [...]

  • Macinsoft - Subclassing NSManagedObject said:

    [...] Zarra on The Mac Developer Network: “Seasoned Objective-C developers are accustomed to certain ways of subclassing objects. While we [...]

  • Aral Balkan - Links for 2009-07-04 said:

    [...] Mac Developer Network » Blog Archive » Core Data: Subclassing NSManagedObject Seasoned Objective-C developers are accustomed to certain ways of subclassing objects. While we definitely subclass objects less often than developers of other languages, it is still necessary in many cases. [...]

  • Axel Roest said:

    Great explanation! Finally some clear and concise info on dynamic properties and related info.

  • Ted Benson said:

    Marcus, this is a very helpful article. Thanks very much! I’ve been having trouble finding this information spread across Apple’s documentation, but this pulls it all together nicely.

  • Eric Cooper said:

    Marcus,
    This is very clear and helpful. I wonder, though, if you could expand on the primitive accessors section and describe how one would override relationship accessors.
    Thanks.

Leave your response!

Add your comment below, or trackback from your own site. You can also subscribe to these comments via RSS.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

This is a Gravatar-enabled weblog. To get your own globally-recognized-avatar, please register at Gravatar.