How To Use Custom Classes With Core Data Without Fear
If you are using CoreData in your iOS app, you have several options for your domain objects. Here is a short list of ways you could use Core Data objects in your app:
- Use generic NSManagedObject, no custom classes
- Create custom classes by hand
- Create a file template to use when creating custom classes
- Use an external tool to create custom classes
- Let Xcode generated custom classes, then modify them
- Add categories to Xcode generated custom classes
My general approach to development is to let Xcode do the tedious bits for me, so my approach to Core Data objects is #6. Let’s take a brief look at the other options first.
1. Simple Approach: No Custom Classes
Pick up almost any book that covers CoreData, and you will probably find the introductory example does not use custom classes at all. That is clearly the proper place to begin when teaching CoreData — you want to introduce topics in manageable chunks when someone is first learning a subject.
If you create a new project using Navigation-based Application template in Xcode, and select the “Use Core Data” checkbox, the initial project will create a simple model with one Entity, ‘Event’ which does not have a custom class.
If your needs are simple, this method may be enough — it is certainly enough to play around with Core Data and learn what is going on. And for simple applications, you can manage the data just fine using the generic NSManagedObject instances . But if you need app-specific domain objects, then you will need to create custom classes for each of your domain objects.
Xcode lets you specify a custom class name on the property sheet for the entity, so let’s look at some ways to use that.
#2 Create custom classes by hand
I have never created a custom class by hand. There is no special magic to it, just some tedious work to make sure you correctly cover all the behavior needed. There can be value in doing things by hand once just to see what is involved in the process, but it is clearly not a useful long-term approach.
#3 Create a file template to use when creating custom classes
If you really have a desire to have non-standard, hand-crafted custom classes, then once you got it as you wanted, you could create a file template for it. I don’t recommend this any more than method #2, other options have the same flexibility with less work.
#4 Use an external tool to create custom classes
Some of the standard tools in many experienced Core Data developer’s toolbox have been Jonathan ‘Wolf’ Rentzsch’s tools: mogenerator and Xmo’d.
mogenerator is a program that builds two custom classes for each entity in a Core Data model: a machine version and a human version.
The machine version handles the basic behavior you need from a custom Core Data class (like Xcode’s generated classes). It is expected that the machine version will get overwritten every time the model changes, so you should not make any changes here — they will be lost.
The human version extends the machine version and is the class you modify for your own app specific behavior.
Xmo’d is a plugin for Xcode 3 that automatically detected changes to the model and regenerated classes as appropriate. Unfortunately, Apple changed something in AppleScript in Xcode 4 and broke something that the Xmo’d plugin needed.
mogenerator works just fine still from the command line, so is still a good choice. There are custom templates based on Jonathan’s work that you can use or modify as well, but you will need to get the project from github to see them.
#5 Let Xcode generated custom classes, then modify them
Xcode will generate custom classes for you based on the entity definition in the model. I like having Xcode generate classes instead of a third party tool so I do not have to worry (as much) about things breaking when new versions of Xcode are released, or missing things that might be part of an updated Core Data framework.
Just as with mogenerator, you can run regenerate these classes any time your model changes, but it will also completely overwrite the existing classes. So, if you choose to modify the generated classes directly, you are forced to do redo your custom work each time — not really a good choice.
Fortunately, there is a straightforward way to avoid this issue.
#6 Add categories to Xcode generated custom classes
Xcode generated custom classes + categories on those classes is a great combination, one that I have used that last several times I have needed Core Data support in an app. It has the benefits of mogenerator, plus the advantage of being a pure Xcode approach.
First, given this simple Core Data model with two entities, Division and Department:
If we assign a Class to each of them, like ‘MyDivision’ and ‘MyDepartment’, then we can ask Xcode to generate the classes by selecting both entities in the Core Data modeler view, right click on the appropriate folder in the project view, and select ‘New File’ from the menu:
Choose NSManagedObject subclass from the ‘Core Data’ group:
And Xcode will generate classes like this:
// MyDivision.h #import <Foundation/Foundation.h> #import <CoreData/CoreData.h> @class MyDepartment; @interface MyDivision : NSManagedObject { @private } @property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSSet* division; @end
// MyDivision.m #import "MyDivision.h" #import "MyDepartment.h" @implementation MyDivision @dynamic name; @dynamic division; - (void)addDivisionObject:(MyDepartment *)value { NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1]; [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects]; [[self primitiveValueForKey:@"division"] addObject:value]; [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects]; [changedObjects release]; } - (void)removeDivisionObject:(MyDepartment *)value { NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1]; [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects]; [[self primitiveValueForKey:@"division"] removeObject:value]; [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects]; [changedObjects release]; } - (void)addDivision:(NSSet *)value { [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value]; [[self primitiveValueForKey:@"division"] unionSet:value]; [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value]; } - (void)removeDivision:(NSSet *)value { [self willChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value]; [[self primitiveValueForKey:@"division"] minusSet:value]; [self didChangeValueForKey:@"division" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value]; } @end
We can now create some category classes to add our own custom behavior and expose the add and remove division methods that are part of the generated implementation class.
// MyDivision+helper.h #import <Foundation/Foundation.h> @protocol MyDivisionMethods <NSObject> @optional - (void)addDivisionObject:(MyDepartment *)value; - (void)removeDivisionObject:(MyDepartment *)value; @end @interface MyDivision(helper)<MyDivisionMethods> @property (nonatomic,readonly) BOOL valid; - (void) myCustomMethod; - (BOOL) validate:(NSMutableArray *) errors; @end
// MyDivision+helper.m #import "MyDivision.h" #import "MyDivision+helper.h" @implementation MyDivision (helper) - (void) myCustomMethod { // do something here } - (BOOL) valid { return [self validate:nil]; } - (BOOL) validate:(NSMutableArray *) errors { BOOL result = YES; // check object state and children for validity // add error messages to errors array when problems are found return result; } @end
As long as you put all your custom behavior in the MyDivision(helper) or MyDepartment(helper) categories, and not in the MyDivision or MyDepartment class, you can have Xcode recreate the MyDivision and MyDepartment classes without worry.
Remember, to make use of the methods implemented or exposed in your category, import both header files in the classes that need that behavior:
#import "MyDivision.h" #import "MyDivision+helper.h" ...
Conclusion
There are many useful bits of Core Data advice out on the web, but this is definitely one area where a good book can help.
There are a number of books out there, many of them good, but since Core Data and Xcode keep changing, you want to find a fairly recent edition of one. For iOS apps, I recommend “Core Data for iOS” by Tim Isted and Tom Harrington.
I am leading a half-day session this September at 360iDev on using OCMock in your automated tests (you are doing automated testing, right?) You should come join us, 360iDev is always a great time for so many reasons.
Also, I am doing two sessions at a new conference, CocoaConf, in Columbus Ohio on August 12th & 13th. It is a new conference, so I can’t tell you about past performance, but it has a great set of featured speakers, and I fully expect it to be a great time. Check it out!
Have a great day!
This is post 7 of 10 on my second iDevBlogADay run.
We all need the support of others to do our best work. Find other like-minded developers that will provide encouragement and motivation through local user groups, conferences or meetups. A great collection of indie iOS developers have helped me stay on track through meetups, 360iDev, twitter, and iDevBlogADay.
I regularly attend Cocoa/iPhone developer meetups in Cincinnati, Ohio and Columbus, Ohio. If you are in the central or southwest Ohio area, come join me at either monthly meetup, or join us in Columbus for CocoaConf in August:
- Cincinnati CocoaDev, iOS and Mac development group, meets the 3rd Thursday of each month in Mason, Ohio
- Columbus iPhone Developers User Group (CIDUG), meets the 4th Tuesday of each month in Dublin, Ohio
- CocoaConf, Columbus Ohio, August 12-13
If you depend on iOS development for your livelihood, or would like to get to that point — you really need to attend a conference dedicated to helping you get better, and I can think of no better conference for that purpose than 360iDev — you should register today!. Much of what I am able to do professionally is due to the things I learned and the people I met there.
Finally, here is a little more information about me, Doug Sjoquist, and how I came to my current place in life. You should follow me on twitter and subscribe to my blog. Have a great day!