Thursday, March 14, 2013

Objective-C Categories and Keeping Functionality Separate

Ever since spending months on a never finished project to tear the rendering code out of a large commercial application and into a QuickLook plugin, I've been hypersensitive to dependencies in my code. All of my code is on a need to know basis from the other objects in the project. There are a variety of techniques in Objective-C to aid you in decoupling your code, but the one I'm writing about today is the category.

Let's say I'm writing a document framework, something I'll be using in many apps, each with different needs. Some apps will need editing, most apps will need importing from documents, some will need writing to documents, most will need onscreen rendering, some will need conversion to export formats.

Naively, I could just add all that needed functionality into each of my class's source files. But the Objective-C category allows me to segregate my code by functional topic.

@interface DocumentNode : NSObject
@property(strong, nonatomic, readonly) NSString* type;
@property(strong, nonatomic, readonly) NSDictionary* attributes;
@property(strong, nonatomic, readonly) NSArray* children;
@property(strong, nonatomic, retain) NSObject* cache;
@property(strong, nonatomic, readonly) NSString* name; // an attribute

-(id) initWithAttributes:(NSDictionary*)startAttributes 
           children:(NSArray*)startChildren 
           type:(NSString*)startType 
           name:(NSString*)aName;
@end

and then if I need to have a method to edit the node because I'm writing an editor and not just a viewer, I can create a separate file with an 'Editing' category:
@interface DocumentNode(Editing)
-(DocumentNode*) cloneWithAttributes:(NSDictionary*)newAttributes;
-(DocumentNode*) cloneWithNewChildren:(NSArray*)newChildren;
@end

or if I need a place for my rendering code
@interface DocumentNode(Rendering)
-(BOOL) renderIntoContext:(CGContextRef)quartzContext  
           withDocumentVisitContext:(DocumentVisitContext*)visitContext;
@end

Now, categories are limited in that you cannot add new member variables to your classes via categories like you could by subclassing your objects. You could use objc_setAssociatedObject to implement the storage for a property, but that would probably be too over the top for just keeping implementation separate. When faced with this design problem, I punted and put the property in the main implementation file, as in the following case where my undo ad redo stacks are unused unless the document needs editing functionality.
@interface GHDocument : NSObject
@property(nonatomic, strong) NSMutableArray* undoStack; // only used if Editing is included
@property(nonatomic, strong) NSMutableArray* redoStack;
In practice, once the code is factored in this way, it can be reused modularly. If I have a project that just acts as a viewer, I just don't include the 'DocumentNode+Editing.h' in my other source files, and I don't compile the 'DocumentNode+Editing.m' file by unchecking it's inclusion in Xcode. For small projects this might not matter much, but when you are building up a large codebase or want to reuse just a small bit in another project, you'll regret not factoring things for modularity from the very beginning.
 
Google+