Overly Complex Models in Core Data

Finesse takes experience. Nowhere is that more obvious than when you are looking back over old code you wrote when you were still learning something.

I've recently been refactoring a pretty significant app I wrote for my employer and since the web API it talks to has been simplified and improved the model on the client needed a bit of a nip and tuck. Well, nip and tuck may be somewhat of an understatement, more like slash and burn. The Core Data model went from 29 entities to three. And that's with all the same features as before plus a few new ones.

The reason that was possible is partially because the server's model got simpler, but it's mainly because I was using Core Data much like you would use an ORM – the entire model was expressed fully as Core Data entities and relationships. It's a testament to the expressiveness of Core Data that this was not just possible, but not that difficult to implement.

At least initially and that's the point of this post. Even with helpers like MagicalRecord there's a fair amount of ceremony that goes along with using managed objects (contexts, fetch requests, predicates, etc.). With networking code for synchronization thrown in, the cognitive overhead of maintaining the application was getting burdensome.

Which Models are Entities?

Not all aspects of your model classes need to be expressed as Core Data entities and relationships. A single instance of an NSManagedObject subclass can store an entire object graph; don't add the complexity of more entities and relationships if you don't really need them.

For example, in my app there is a "work order" that has one or more assignments. An assignment is a user with a name, id, email, status, etc. Originally this was modeled as three entities with reciprocal relationships – WorkOrder, User, and Assignment entities with a to-many relationship between WorkOrder and Assignment and a to-one relationship between Assignment and User and inverse relationships going the other way.

Sounds great and it worked fine, but it add a lot of unnecessary complexity. The assignments for a work order where always "read only" from the client and just downloaded and updated from the server. Of course, this required synchronization logic to update or remove old assignments and create new ones. It was also a lot of boilerplate to manage all the entities.

This was simplified with one of my favorite refactoring techniques, "delete a bunch of code", by just removing the Assignment and User entities and their associated relationships. Instead, the WorkOrder entity now has an "assignments" property that stores an array of dictionaries. Just make the type of the property "transformable" on the entity and you store just about anything in it.

This simplifies things a lot:

  • No more synchronization logic. Just set the assignments property wholesale on each update.
  • Fewer classes; no more Assignment or User class, just simple framework classes and very little parsing.
  • Less fussiness populating the view.

You're not even restricted to framework classes like NSArray, NSDictionary, NSString, NSNumber, NSDate, NSData, etc. As long as your classes implement the NSCoding protocol you'll be able to use Plain Old Objective-C Objects (POOCO's?).

When You Really Need an Entity

Go into this with eyes open, though. There are trade offs.

You won't be able to apply Core Data capabilities to things that are not expressed as entities. For example, whereas before I could find work orders for a given user using a predicate (e.g. assignment.user.name == %@) now I cannot do that as easily. It can be done, but requires a block predicate that ends up loading every work order to evaluate it. You may still be able to get around it by adding more internal entity properties which are just for searching, also.

Hiding the Implementation Details

The downside to a transformable property in Core Data is that the type of property will by id. Plus, you'll need to worry about nil and non-mutable arrays or dictionaries.

To simplify this I made the entity property private and exposed a read-only mutable collection on top of it that handles initialization and mutability.

Looking at the WorkOrder example, it has an assignmentsValue property which is hidden internally and a read-only, public accessor. Here's a snippet of the header:

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface WorkOrder : NSManagedObject

@property (nonatomic, retain) NSNumber * key;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, readonly) NSMutableArray * assignments;

@end

On the implementation I front end the actual assignment value:

#import "WorkOrder.h"

@interface WorkOrder () {
    NSMutableArray *_assignments;
}

@propery (nonatomic, retain) id assignmentsValue;

@end

@implementation WorkOrder

@dynamic key;
@dynamic name
@dynamic assignmentsValue;

- (NSMutableArray *)assignments
{
    if (!_assignments) {
        _assignments = [NSMutableArray arrayWithArray:self.assignments];
        self.assignmentsValue = _assignments;
    }

    return _assignments;
}

@end

This allows you to simply start using the assignments and have it lazily initialized or wrapped in a mutable array as needed.

UPDATE: It occurs to me I've glossed over what's involved if you want to update the values you've stored in a transformable property. If you're just replacing the value with a new one, that will work just fine. On the other hand, if you're modifying a mutable collection or changing the properties of a previously stored object, it's a little more complex. I do a follow up post shortly on how that works.

Home