in iOS

Objective-C Categories

Categories gives developers the possibility to add functionality  like methods and properties  to an existing class without the need to subclass it first.

This has some advantages, one being able to divide the code into a more specialized parts that’s used only when needed.

How does it work

Adding methods

Implementing categories is as simple as defining a new @interface for it in a definition file (UIView+myCategoryName.h), something like this:

@interface UIView (myCategoryName)
 
@property (readonly,nonatomic) NSNumber* xyzRadAngle;
-(void) xyzMakeItRotate:(CGFloat)rad_angle;
 
@end

The syntax of the @interface directive is

 @interface Object_to_categorize (category_name)

The implementation file (UIView+myCategoryName.m) looks something like this:

@implementation NSString (myCategoryName)
@dynamic xyzRadAngle;
 
-(void) xyzMakeItRotate:(CGFloat)rad_angle
{
    // apply a rotation transformation
    self.transform = CGAffineTransformMakeRotation(rad_angle);
}
 
@end

To call on this category, we simply #import “UIView+myCategoryName.h”,  into a file or class where it will be used and access the xyzMakeItRotate function. Once imported all UIView’s (class and subclasses) in that file will now have an extra function called xyzMakeItRotate. Neat! right?

Adding properties

In the above example, I have added @property in the definition file (.h). I also added @dynamic directive in the implementation file (.m). Setting @dynamic directive tells the compiler that we will define the getters and setters for this property.

For categories, since they are not an actual class, no getters/setters are defined by default at compile time, we have to do this manually on our own.

Categories add functionality to a pre-existing class. These classes has a predefined set of properties and stores that is configured at compile time. If your category requires an extra property for the categorized class you’re out of luck. The proper way to add new properties is to subclass the said class, that’s what subclassing is for.

Another option is static variables. We can define static variables and store the data there. It is not recommended and that gets messy really quick once you start using your category in multiple places.

Q: So how do we store data for category properties? Answer: AssociatedObjects at runtime.

NSHipster has a great write-up about the subject that I recommend you go read.

Associated Objects

What are Associated Objects? In Objective C, all NSObjects have an NSDictionary (Hash lookup table) that stores objects at runtime. Developers can use this to pass runtime objects between running processes.

To use Associated Objects, we need to import <objc/runtime.h> into our category. Once imported, we have access to two functions:

 
    objc_getAssociatedObject(
                             // Object where the Associated object is located
                             id object,
                             // String key (for Hash map lookup) where the object is saved
                             const void *key);
 
    objc_setAssociatedObject(
                             // Object where the Associated object is located
                             id object,
                             // String key (for Hash map lookup) where the object is saved
                             const void *key,
                             // Object to save
                             id value,
                             // Type of association: COPY/ASSIGN/RETAIN, ATOMIC/NONATOMIC, etc.
                             objc_AssociationPolicy policy);

I have commented the code above to briefly explain each parameter. Check out Apple’s documentation for more details.

So after this light primer about Associated Objects, let us go back to our category implementation.

Creating a Category’s Property

We defined xyzRadAngle property as readonly, so we only need to write a getter:

-(NSNumber*)xyzRadAngle
{
    NSNumber *retval = @0.0f;
    return retval;
}

To apply what we just learned about AssociatedObjects we will modify xyzRadAngle‘s getter to the following:

-(NSNumber*)xyzRadAngle
{
    NSNumber *retval = @0.0f;
 
    // get associated object attached to this UIView at location 'key'
    NSNumber *number = objc_getAssociatedObject(self, &key);
 
    // make sure that there was an object saved,
    if(number) {
        retval = number;
    }
    return retval;
}

The object key is a NSString with any arbitrarily chosen value that must be unique for this object. Common practice is to use the property name followed with the string “Key”. Here’s an example of how to define key

static NSString *key = @"xyzRadAngleKey";

Ok, now xyzRadAngle property is returning an NSNumber object saved in the AssociatedObjects hash map of the UIView.

At this point, the xyzRadAngle will always return a value of @0.0f since we have not set the associated object anywhere in the code,

That’s ok, we will do it now.

Back in xyzMakeItRotate method, we will add a call to objc_setAssociatedObject like so:

-(void) xyzMakeItRotate:(CGFloat)rad_angle
{
    // apply a rotation transformation
    self.transform = CGAffineTransformMakeRotation(rad_angle);
 
    // saving rad_angle as associated object means we must
    // first cast CGFloat into NSNumber then save that object
    // to retreive, we retreive an NSNumber
    NSNumber *radAngleNumber = [NSNumber numberWithFloat:rad_angle];
    objc_setAssociatedObject(self, &amp;key, radAngleNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 
}

That’s it! Congratulations, you now officially know how to create Obj-C categories!

A full demo project can be found here: github

Considerations

Despite its ease of use and versatility, one must take care when using and implementing categories as name conflicts in method names and properties may occur between different categories and classes/subclasses.

Take for instance this (totally wrong on all levels) NSString category implementation:

//
//  NSString+mikeString.m
//  testCategories
//
 
#import "NSString+mikeString.h"
 
@implementation NSString (mikeString)
 
-(BOOL)isEqualToString:(NSString *)aString
{
    BOOL retval = YES;
    const char* ptr1 = @"mike".UTF8String;
    const char* ptr2 = aString.UTF8String;
 
    if(strlen(ptr2) == strlen(ptr1)) {
        for(int i=0;i&lt;strlen(ptr1);i++) {
            if(*(ptr1+i) != *(ptr2+i)) {
                retval = NO;
                break;
            }
        }
    } else {
        retval = NO;
    }
 
    return retval;
}
 
@end

This implements a category that overrides isEqualToString method of NSString. the isEqualToString method now checks wether any string we are comparing is equal to ‘mike’.

Because of this namespace conflict, we are no longer using NSString’s original method. Convention recommends that we prefix our category methods and properties with a three letter prefix (according to Apple) to give the method and property its own ‘namespace’.

The moral of this story: care must be taken when choosing category method and property names.

Cheers!