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:

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 parentheses. 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 parentheses- 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 then 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

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


Previous Lesson Next Lesson