Objective-C Lesson 8: Categories

An Objective-C category allows you to add methods to an existing class -- effectively subclassing it without having to deal with the possible complexities of subclassing.

Some situations in which you may want to use categories include:

  • You want to split the functionality of a class into multiple files (for many of the same reasons you’d want to create classes in the first place—ease of use, less to maintain at a time, etc.)
  • You’re working with other people on the same classes and you’ve been tasked with handling certain parts of that class (drawing to screen, for example)
  • You don’t want to deal with subclassing a class—for example, in NSString‘s subclassing notes, it states that “doing so requires providing storage facilities for the string (which is not inherited by subclasses) and implementing two primitive methods. The abstract NSString and NSMutableString classes are the public interface of a class cluster consisting mostly of private, concrete classes that create and return a string object appropriate for a given situation. Making your own concrete subclass of this cluster imposes certain requirements…”
  • You need to add methods to a class that you don’t have access to, such as the Foundation classes (NSString, NSArray)

In all of these cases, categories provide a perfect solution. As an example, we are going to add a method to NSArray that will return a random, non-repeating object from an array. This could be used, perhaps, in a word game, where you might have an array of NSString words and want to use each word exactly once.

Jumping right in, we are going to put our category into a separate file—we don’t have access to the original NSArray files. If we did, anything that used NSArray could use our method…but since we don’t, we’re going to have to import our file before we use it.

The naming convention of category files is OriginalClassName-CategoryName. Therefore, create two files, NSArray-RandomObjects.h and NSArray-RandomObjects.m. In the former, place this code:

@interface NSArray(RandomObjects)
-(id)randomObject;
@end

Note that it simply looks like a class declaration—except for the part in bold (emphasis added). That part tells the compiler that you’re defining a category, not a new class. The name is used for documentative purposes (telling yourself what it does) and is effectively irrelevant elsewhere. And on the subject of new classes, note that you cannot add new instance variables to a class—this goes back to the dynamic nature of methods (bonus points to anyone who can explain why).

In the corresponding .m file, place the implementation of that method, as usual:

@implementation NSArray(RandomObjects) - (id)randomObject { // You could also pass in an array as an argument
    self.randomArray = [[self.randomArray shuffledArray] mutableCopy];
    NSInteger index = arc4random() % [self.randomArray count] - 1;
    id object = [self.randomArray objectAtIndex:index];
    [self.randomArray removeObjectAtIndex:index];
    return object;
}
@end

Note again the bolded text—that is the only syntactical difference between a category and a class definition.

A test program might look like this:

#import "NSArray-RandomObjects.h"
int main(int argc, char *argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSArray *words = [NSArray arrayWithObjects:@"Curb", @"Days", @"Fins", @"Dish", @"Cite", @"Chat", nil];
    for (NSInteger count = 1; count < [words count]; count++)
        NSLog(@"Next object is %@", [words randomObject]);
}

If you try to build and run now, you’ll get a compiler warning. And thin the program will crash. Turns out, the randomArray method we’re using in our category method comes from yet another category. Check it out, import the new header in NSArray-RandomObjects.h, and you should be good to go.

A Few Notes

  • A category method can override an existing method, but you’ll lose access to the original outside of that class. Therefore, if you do override, make sure you duplicate existing functionality, or use a call to super.
  • If a method is defined in more than one category of a single class, which version is invoked is undefined.
  • Remember that methods that you add to a class are inherited as well. If you add methods to NSObject, every class will have access to those methods.
  • Added methods may serve your purpose, but may go against the design of the class. Adding the ability to handle multiple strings simultaneously to NSString blurs the line between NSString and NSArray.
  • Object / category name pairs must be unique in any namespace. There can only be one NSArray-RandomObjects in a namespace. The default namespace is shared between all classes, libraries, frameworks, and plug-ins. This can be a hazard for developers of those products, especially since they get injected into other code.

This post is part of the Learn Objective-C in 24 Days course.

0 comments


Or enter your name and Email
No comments have been posted yet.