The importance of custom primitive properties in CoreData

The topic of this week’s post is responsible for hours of frustrating debugging that I went through the first time I developed a large project using CoreData.

CoreData is really great once you learn how to use it properly, but it’s learning curve is pretty steep. And as if to complicate things a little further, the templates xcode is providing are pretty useless at best, and down right buggy in several cases.

Lets see an example where xcode’s template can cause severe bugs:

When creating a CoreData entity, we can use xcode’s templates to generate a NSManagedObject subclass for the new entity we created, by selecting “Create NSManagedObject subclass..” for the Editor menu.

Basically, it generates a class with properties for each attribute we defined. (I’m ignoring the relationships in this post)
Lets look at a sample Entity with two attributes: a string called “name” and a boolean called “isUnique”. The generated code looks like this


// Header file

@interface MyObject : NSManagedObject {
@private
}
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * isUnique;

@end

// Implementation file

@implementation MyObject
@dynamic name;
@dynamic isUnique;

@end

The core problem in this template is the fact that primitive types are saved as NSObjects. Note in our example that the boolean var isUnique is saved as an NSNumber instead of BOOL.
This is actually very beneficial, because CoreData is an object graph system and using NSObjects allow it to handle most of the heavy lifting when moving between architectures (bit/byte ordering, floating point representation issues, etc.)

The problem with storing NSObjects instead of primitives starts when we try to compare objects. using the compare operator (==) on primitive vars will simply comare the numbers, but using it on two NSNumbers will compare the pointers obviously not what we intended to do.

The most common scenario for the pointer comparison bug is when validating bool values. For example, the following innocent line:

if (myObject.isUnique) {
    // do something
}

Will always evaluate to true!. This is because we are simply checking if the pointer is not nil, and not whether or not its value is YES.
Not only that, it will not produce any compile errors or warnings!

The simplest solution is to use NSNumbers “boolValue” method like this:

myObject.isUnique = [NSNumber numberWithBool:NO];
if ([myObject.isUnique boolValue]) {
    // do something
}

However, and trust me on this – You WILL forget this line sometimes. And bugs causes by expressions evaluating to true, are some of the hardest to find.

A better solution is to override the basic implementation of the CoreData properties, expose the primitive values, but save the NSNumber values to the context.
To do this we have to change the implementation of the NSManagedObject like so:


// Header file

@interface MyObject : NSManagedObject {
@private
}
@property (nonatomic, retain) NSString * name;
@property BOOL isUnique;

@end

// Implementation file

@implementation MyObject
@dynamic name;

-(BOOL) isUnique
{
    [self willAccessValueForKey:@"isUnique"];
    BOOL isUnique = [[self primitiveValueForKey:@"isUnique"] boolValue];
    [self didAccessValueForKey:@"isUnique"];
    return isUnique;    
}

-(void) setIsUnique:(BOOL)newIsUnique
{
    [self willChangeValueForKey:@"isUnique"];
    [self setPrimitiveValue:[NSNumber numberWithBool:newIsUnique] forKey:@"isUnique"];
    [self didChangeValueForKey:@"isUnique"];
}

@end

That’s it. Now you can use the properties as regular primitives and avoid bugs.

There’s a tool that does this automatically called mogenerator. It’s somewhat of an overhead if you don’t have a lot of NSManagedObject files, or if they’re very small, but it’s pretty awesome in most cases.

Hope you enjoyed this post, and maybe save a couple of hours of frustrating debugging.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: