Skip to content

Object-C Categories

When I read about Object-C Categories the first time I did not really understand what was good for. But as usual I need to actually use it in order to understand it but at least I had an idea where I could use it.

So what is it good for. Well, let’s have a look at the problem I faced. For the Minibloq project I used a quite elaborate cell and because there is a lot of code that needs to be handled within the cell I created a View Controller for the content of the cell. This is not too difficult to do until I had to code the scenario when a cell is reused. Then I need to get a hold of the View Controller in order to adjust its settings. Unfortunately there is no reference on the cell and so I would need to store that in the table’s view controller but that is not very elegant and use too much code. So what if we could add a new property to the cell that would contain a reference to my view controller so that when I get the cell back I can just ask for the view controller and then use it there. That is exactly what the Categories are good for.

If you are familiar with Aspect Oriented Programming a Category is what in AOP is called a Mixin which in other word is an dynamic extension of a class. That makes it possible to change or extend a class where we might not have the source code available or cannot change the code ourselves.

Create a new Category in XCode 4

1. Select “New File…

XCode.Select.New.File.from.Context.Menu

2. Select the “Object-C Category” from the iOS section

XCode.Select.Object-C.Category.png

3. Specify the Class this Category is based on

XCode.Enter.Base.Class.for.Category

4. Specify the Name of the Category

XCode.Enter.Object-C.Category.Name

Attention: the name of the category is per convention: +

Code the Category

So far I created a category that will extend the UITableViewCell and allows me to add functionality to the base class. So I could create a property containing a reference for the View Controller and so we are done. But not so fast was what XCode 4 was telling me. It turns out that I cannot synthesize a property or use a property inside a Category and so I had to use some magic.

The header looks like this meaning there is nothing special about the header:

#import <UIKit/UIKit.h>

@interface UITableViewCell (UITableViewCell_ViewController)

@property (nonatomic, retain) UIViewController* viewController;

@end

Looking into iOS I found that I could use its runtime environment to store a property:

#import "UITableViewCell+ViewController.h"

#import <objc/runtime.h>

static char STRING_KEY;

@implementation UITableViewCell (UITableViewCell_ViewController)

- (UIViewController*) viewController {
    return (UIViewController *) objc_getAssociatedObject( self, &STRING_KEY );
}

- (void) setViewController:(UIViewController *)aViewController {
    objc_setAssociatedObject( self, &STRING_KEY, aViewController, OBJC_ASSOCIATION_RETAIN_NONATOMIC );
}

- (void) dealloc {
    objc_setAssociatedObject( self, &STRING_KEY, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC );
    [super dealloc];
}

@end

Finally we can use it. The only think we have to make sure of is that we import the Category wherever we use it.:

#import "UITableViewCell+ViewController.h"

Use the Category

Then we handle a Table Cell as if it is could reference a view controller:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell;
    NSString *cellIdentifier = @"ProgramCellView";
    cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if( !cell ) {
        [[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:self options:nil];
        cell = myTableCell;
        InstructionViewController* instructionVC = [[InstructionViewController alloc] init];
        [cell addSubview:instructionVC.view];
        // When we create a new Cell we need to set the View Controller as well
        cell.viewController = instructionVC;
        [instructionVC release];
    }
    // ...
    // Now we can obtain a reference to its view controller whenever we need it.
    InstructionViewController *instructionVC = (InstructionViewController *) cell.viewController;
    // ...
    return cell;
}