Don’t Write a Class, Write a Category!
As an iPhone freelancer, I develop a lot of iOS apps. A lot. And one of the most important things to my business is that I leverage as much of my past work as possible when it comes to new projects. I have several strategies, but today I wanted to share one of my favorites: categorization.
If you’re an experienced developer who’s worked with various languages and platforms over the years, you’ll know that the classic way to add “core” functionality was to either extend a class or create a new (probably static) class. We’d create classes like BetterString (extends String) or StringUtils as ways to add functionality to the platform we think could be handy later.
Of course, the problem with extending classes is that they’re not always interoperable with library code. Taking the example above, you might have to wrap your String in a BetterString before passing it to your code. It gets ugly. Creating new classes separates the functionality out a bit, but providing a bunch of static methods from StringUtils just feels disconnected and incorrect. (Hint: it is.)
Abuse the $*#@&^ Out of Categories!
Generally, when we think about categories, the first thing that comes to mind is adding useful NSString or UIColor methods. In InnerBand, I provide popular UIColor#-colorWithHex: and NSString#contains: methods that can be re-used all over the place without interfering with the categorized classes.
But what if I told you that using categories for app-specific functionality unlocks their true power?
You see, the best use for categories is dropping ALLLL the reusable bits you need down to the microscopic level. How much do you use categories? Answer yes or no to these questions:
- Do you specify fonts or font sizes in place when you need them?
- Do you specify colors all over your code?
- Do you access NSUserDefaults values by providing keys where you access them?
- Do you check for NSNull when you’re parsing JSON?
Did you answer yes to any of these? If so, you need to abuse categories more. You see, as a Clean Coder, it’s your mission to isolate these kinds of choices and checks as much as possible.
If you’re still reading, you want to know more. So let’s inspire you! Here’s how we can put categories to good use!
Pre-Define Your Fonts in a Category
If you’re specifying a font face or size in your view controller, you’re doing it wrong. You should define the fonts you’ll use in one place and reference them elsewhere. And we’ll use a category:
@implementation UIFont (AppFonts) + (UIFont *)titleFont { return [self bebasFontOfSize:20]; } + (UIFont *)subtitleFont { return [self bebasFontOfSize:15]; } + (UIFont *)bebasFontOfSize:(NSInteger)size { return [UIFont fontWithName:@"Bebas Neue" size:size]; } @end
As a bonus, notice how I even have a convenient private method for creating the Bebas Nueue font in case I ever need to change it.
Create Your Colors in a Category
Colors are tough in XCode. First, I DO NOT LIKE the Apple format of using decimal numbers to define them. Designers don’t use that format, it’s harder to eyeball the distributed syntax, and sooo many apps that can help you with color will output in Hex rather than Apple’s format. (If you insist on using the Apple format, I use and recommend Color Snapper.)
Past that, even if you do use hex colors, you should confine them to a category:
@implementation UIColor (AppColors) + (UIColor *)headlineColor { return [UIColor colorWithString:@"#ff0000"]; } + (UIColor *)standardBackgroundColor { return [UIColor colorWithString:@"#111111"]; } @end
Categorizing your colors in this way makes them easy to change and locates them right on the class you’ll find the most convenient. Look at the client code:
self.view.backgroundColor = [UIColor standardBackgroundColor];
Contrast that with this macro/function usage:
self.view.backgroundColor = MA_COLOR_STANDARD_BACKGROUND_COLOR();
The former just feels so natural. Naming is shorter and more readable, and I know where to find it should I need to look at it.
Don’t Re-Use the Key, Re-Use the Access
One of the more prevalent anti-patterns I’ve seen is to create a constant for use as an NSUserDefaults key. I know, my old code does it, too. :-) And yes, constants are good for this sort of thing to avoid typos, but using categories we can make saving and restoring these values even more reusable and easy to do:
// keys static NSString * const kUserDefaultsDealerId = @"kUserDefaultsDealerId"; @implementation NSUserDefaults (AppDefaults) - (void)setDealerId:(NSString *)dealerId { [self setObject:dealerId forKey:kUserDefaultsDealerId]; } - (NSString *)dealerId { return [self objectForKey:kUserDefaultsDealerId]; } @end
You can see that I use a constant for the key, but the constant and the accessors are all here on this NSUserDefaults category and, again, all very logically located. (Assuming you don’t have a separate model class.) I can read and write the value, and I do it right on the class:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setDealerId:@"ABCDE"]; [defaults synchronize];
It just feels so natural to perform these operations on the very class we intend to interact with. Notice I don’t do the synchronize in the call because there are cases where doing so would be very ineffecient, such as when you were setting the value within a loop.
Even the Lowest Level Code Can Be Categorized
I love JSON, but it can be a bit annoying to constantly check for NSNull in values you receive. (You are doing that, right?). In the old days, you may have seen a lot of code looking this:
NSString *firstName = [json valueForKey:@"first_name"]; self.firstName = (firstName && firstName != (NSString *)[NSNull null]) ? firstName : nil;
Repeated everywhere. Well, let’s categorize!
self.firstName = [json safeValueForKey:@"first_name"];
We can reuse it everywhere. What’s it look like?
@implementation NSDictionary (Nulls) - (id)safeValueForKey:(NSString *)key { id value = [self valueForKey:key]; if (value && value != [NSNull null]) { return value; } return nil; } @end
Final Thoughts
Categories are a powerful part of the Objective-C language and, for once, one that’s been there quite a while. (*cough* blocks *cough*) They get decent use in every codebase I’ve seen, but I think many stop short of truly abusing them. Hopefully, I’ve turned you into a frequent abuser today!
One last thing, I am a proponent of importing these in the PCH. Every one. Why? Well, if you take the concept of “forgotten code” as code that doesn’t get used by developers who weren’t aware the functionality was there, then the advantage of importing these in the PCH is that a new developer can more easily stumble on these without even being told. They go to define a new font and…hey, what’s that? A -headlineFont method? I think it’s a great way to help developers find their way around the codebase.
Alright, that’s all I got. Code away!
My Concise Introduction to CocoaPods How to Memorize Block Syntax
Comments are currently closed.
2 thoughts on “Don’t Write a Class, Write a Category!”
I’m also thinking about this recently. Those class methods like +titleColor can be place in arbitrary category or factory object, which gives me a headache. I use to put them in a factory object like `ColorFactory`, but after reading your post I do want to try the category approaching. But I worry about naming collisions with other people’s code, I could prefix every method I create but that just looks ugly.
I totally get the naming prefix stuff in categories, but I’ve never been a big believer in it. I think the only time it’s really necessary is when your category method names are overly generic (-contains or -isExactlyEqual) and you’re providing a 3rd party framework. Even then…it really depends on how I feel that day.
In the case above, this is local code, and so even if yer affected, it’s going to be a quick rename. It would be overkill to prefix all your methods, you’d waste more time doing that than just adjusting in the rare case a collision occurs. :-)