Some fun and magic with Objective-C categories

Categories are the way you can extend existing Objective-C class without subclassing. Even without having a source code of existing class.

Using categories you can add methods to existing classes (even to Foundation and UIKit classes). And you’ll be able to use them in your app. Also, you could replace existing methods with your implementation. But that’s not the topic of this tutorial. You should consult Apple documenation on categories for more information.

For example, let’s add a method to NSString to display it in alert.

@interface NSString (Alert)

-(void)showInAlert;

@end

Now we need to implement this method.

@implementation NSString (Alert)

-(void)showInAlert
{
    [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Debug", @"Debug title")
                                message:self
                               delegate:nil 
                      cancelButtonTitle:NSLocalizedString(@"Ok", @"Ok button title") 
                      otherButtonTitles:nil] show];
}

@end

Very simple. And now you can show any NSString as alert in your application.

- (IBAction)alertText
{
    [self.textField.text showInAlert];
}


What about properties?

You can’t add instance variables to existing class using categories. This is due to the fact that instance variable will change memory size needed for object instance. That limitation affects usage of categories a bit… But, luckily, we have powerful Objective-C runtime which could offer us Associated Objects.

This runtime API allows us to “connect” one object instance to another. And we could use it to store additional values we want to attach to class. You should use two functions provided by runtime.

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, void *key);

objc_setAssociatedObject associates value as key to object. objc_getAssociatedObject retrieves stored object for key.

Note that key is void * – basically a pointer, not a NSString. Therefore we should use something which is constant during program execution.

Usually, developers used something like this as a key.

static NSString *kObjectValueKey = @"ObjectValueKey";

However, recently I’ve found another approach in TUAW blog. It looks very simple and effective.

Let’s imagine we want to add property to NSString. We declare a HiddenValue category.

@interface NSString (HiddenValue)

@property (nonatomic, copy) NSString *hiddenValue;

@end

Now we need to implement getter and setter for this property.

@implementation NSString (HiddenValue)

-(void)setHiddenValue:(NSString *)hiddenValue
{
    objc_setAssociatedObject(self, 
                             @selector(hiddenValue), 
                             hiddenValue, 
                             OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)hiddenValue
{
    return objc_getAssociatedObject(self, 
                                    @selector(hiddenValue));
}

@end

We’re using getter selector of our property as key. This ensures that the pointer will be unique and will not change during program execution. I would strongly recommend this convention for adding properties as categories.

Our example has one more interesting side effect. We’ve added hiddenValue property of class NSString. Therefore, this property also has it’s own hiddenValue.

NSString *value = @"value";
value.hiddenValue = @"Hidden value 1";
value.hiddenValue.hiddenValue = @"Hidden value 2";
value.hiddenValue.hiddenValue.hiddenValue = @"Hidden value 3";
// you can continue if you want

Using these techniques you can extend any class in a way you want.

As always do not forget to read Apple documenation on extending classes and using Objective-C runtime.

Sample project demonstrating usage of categories is available on GitHub.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s