Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Objective-C Categories

I’m writing today about categories in Objective-C, and I’m going to be discouraging about it. First let’s recap what categories are to ensure we’re on the same page:

What categories are:

//UIView+ViewAdditions.m
@interface UIView (ViewAdditions)

What categories aren’t:

//UIViewSubclass.m
@interface UIViewSubclass ()

So what’s the difference? It’s not just that one is a subclass; the difference is the name in the brackets (the category name). The first example is adding whatever functions I put in there to the existing class defined in some other file. In the common use case, that usually means adding some convenience functions. The second example is simply adding to a class that I’ve declared locally — a class extension. If you try to do an extension on a remotely declared class you’ll be politely told off by the compiler. The (only) use case for class extensions is making the functions and properties you declare there “private”. Now the initiated among us know that nothing can be truly private in Objective-C, but these “private” functions will not be obviously exposed outside of the class’ functions.

What categories can do for you

Let’s say for a moment that you wanted to add a function to every UIViewController (and subclass) in your entire app. Perhaps you’re trying to use a custom navigation back button and have come upon the infuriating limitation that custom views are ignored on the backBarButtonItem. In this case, you might declare a category on UIViewController adding a function called customBackButtonTapped: to trigger a push and mimic a back button. You could then call it on any controller you like — you could even add another function that adds the custom back button for you… slick.

Why you should avoid the use of categories

The objective-C runtime is an interesting creature, methods and classes and everything else that make up your app are loaded ad-hoc as they’re needed. There are some cool consequences of that, one is that you can modify literally any class or function dynamically at runtime — for instance, you could have a logging function which you can actually swap out depending on how the reporting is to happen. That functionality is exploited by categories to allow you to add these methods to existing classes, and precisely because of that they have a (low) potential for terrible consequences.

What do you suppose would happen if two categories tried to add the same function to an existing class? One would of course have to win, and anything counting on the losing function would start behaving very strangely — usually crashing horribly. The problem is trickier than you might think. You might guess that if you create a new category method and all hell breaks loose you’ll know what to look for — you wish! Remember that I said the runtime loads things in an ad-hoc manner, which means that unless your app utilizes a single thread and you do all of your testing and debugging with a release scheme, you could be in for a surprise.

Release schemes change a lot about your app, including what optimization strategy and what private functions are used from the Apple frameworks. That means you could introduce a category and have tested, added new functionality and forgotten about the category completely by the time that the actual conflict arises (and only in Release nevertheless! debug hell!). You might think that’s alright, because you can be super careful and always be sure to test all of your categories in all schemes before moving on — wrong.

Here’s the real problem with categories — as if the rest wasn’t enough. Again, remember that all of this is ad-hoc, and you’re operating in a multi-threaded environment. Let’s say you do all of your testing and are absolutely confident that the new category you added is bullet-proof, you’re happy. Unfortunately, you then make some changes to one of your operations, be it an animation with a completion block or an NSOperation in a queue or any number of things that use threading. The effect of that change speeds up — or slows down — your operation so that it’s category gets run just a few microseconds later (an eternity for the processor) and suddenly the loading order of things has changed; that category you added two months ago is suddenly causing a conflict — worse, only in Release mode, and even worse only on the older devices because they have less cores!

I think you’re starting to see the issue. Good luck debugging that…

Now let me bring this back to reality for a minute, the likelihood of encountering what I’ve described is extremely, astronomically remote. The point isn’t that you’ll run into this twice a week — in fact you will almost certainly never run into the doomsday scenario I described in the last paragraph; static linking and proper category naming reduce the chances considerably. What I want to do is illustrate that for the few lines of code you may save using a category you may cost yourself hours or days of debug time, and that just isn’t a good trade.

What you should use instead:

  • Subclasses and helper classes
  • C functions
  • method swizzling

Subclasses and Helper Classes

Lets modify our previous example to work without categories. The included code provides a storyboard segue that implements a custom push, which sets up the custom back button as part of the push mechanism. The segue is even an option in the storyboard so you only have to select the “custom push” segue and everything else happens completely behind the scenes — categories eat your heart out!

// CustomNavigationAdditions.h
// Created by Adrian Kemp
#import <Foundation/Foundation.h>

@class CustomNavigationBackButton;
@interface CustomNavigationAdditions : NSObject

+ (UIBarButtonItem *)customBackButtonForViewController:(UIViewController *)viewController;

@end

// CustomNavigationAdditions.m
// Created by Adrian Kemp
#import “CustomNavigationAdditions.h”
@interface CustomNavigationBackButton : UIButton

@property (nonatomic, weak) UIViewController *viewControllerToPop;

@end
@implementation CustomNavigationBackButton

@end
@implementation CustomNavigationAdditions

+ (UIBarButtonItem *)customBackButtonForViewController:(UIViewController *)viewController {
	CustomNavigationBackButton *newBackButton = [CustomNavigationBackButton buttonWithType:UIButtonTypeCustom];
	newBackButton.viewControllerToPop = viewController;
	[newBackButton addTarget:self action:@selector(customBackButtonPressed:) forControlEvents:UIControlEventTouchDown];
	[newBackButton setTitle:@"BACK" forState:UIControlStateNormal];
	[newBackButton sizeToFit];
	UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:newBackButton];
	return backBarButtonItem;
}

+ (void)customBackButtonPressed:(CustomNavigationBackButton *)sender {
	[sender.viewControllerToPop.navigationController popViewControllerAnimated:YES];
}

@end

// CustomPushSegue.h
// Created by Adrian Kemp
#import <UIKit/UIKit.h>
@interface CustomPushSegue : UIStoryboardSegue
@end

// CustomPushSegue.m
// Created by Adrian Kemp
#import “CustomPushSegue.h”
#import “CustomNavigationAdditions.h”
@implementation CustomPushSegue

- (void)perform {
	UIViewController *sourceController = self.sourceViewController;
	UIViewController *destinationController = self.destinationViewController;
	destinationController.navigationItem.leftBarButtonItem = [CustomNavigationAdditions customBackButtonForViewController:destinationController];

	[sourceController.navigationController pushViewController:destinationController animated:YES];
}

@end

C Functions

There isn’t much to this, since Objective-C is a straight super set of the C language, you can leverage anything that C affords you to help you out. You might feel compelled to implement a category on UIColor to give you a colour based on an RGB value. Instead, you could do something simple like:

UIColor UIColorFromRGB(NSInteger rgbValue) {

	return  [UIColor colorWithRed : ((float)((rgbValue & 0xFF0000) >> 16))/255.0 green : ((float)((rgbValue & 0xFF00) >> 8))/255.0 blue : ((float)(rgbValue & 0xFF))/255.0 alpha : 1.0];

}

Sweet.

Method Swizzling

Method swizzling and categories are very similar for a very simple reason — categories use a one-way method swizzle. What you are doing in method swizzling is replacing or injecting the implementation of a method at runtime. Generally when we talk about swizzling we are referring to switching out a method’s implementation just for a minute and then replacing it but the principles can be used in many ways. Let’s take a look at some of the function that allow for switching and adding implementations:

class_addMethod
Adds a new method to a class with a given name and implementation.

class_replaceMethod
Replaces the implementation of a method for a given class.

resolveInstanceMethod:
Dynamically provides an implementation for a given selector for an instance method.

There are about a dozen other functions that you can use in the process of changing a class during runtime, you really should read all about it in the aptly named runtime.h. The important thing about these functions is that they take and return pointers to the methods and implementations. That means that you can check the implementation you’re about to run to determine whether or not it’s actually the one you want — that beats crashing any day.

That’s as far as we go today, method swizzling itself could take another dozen blog posts to discuss and there are many tutorials out there already.

Comments