Objective-C categories are cool. They allow you do something that you can’t do in most compiled languages: add new methods to existing classes. You can even add methods to classes that you didn’t write.
Suppose you wrote some code to translate an ordinary string, like “Hello, perhaps I could enjoy a bit of your delicious sandwich over there?” to LOLcat speak, like “I can has cheezburger?”
In other languages you would probably subclass the String object and add a member function called
-lolspeek which does the translation. This is kind of lame, though, because you could only call
-lolspeek on string objects that you explicitly create from your subclass. You can’t even do this much in Java because the string class is final, so you couldn’t subclass it anyway.
In a real application you acquire strings from all over the place. They come from text fields. They come from property list files. They come from over the network or via Distributed Objects. It would be nice to be able to
lolspeek any string, no matter where it comes from. Categories come to the rescue:
@interface NSString (BNRLolSpeek) - (NSString *) lolspeek; @end // BNRLolSpeek
This category on
NSString makes any
NSString feline-readable, no matter where it originates from.
That’s the cool part. There are some downsides, though. The biggest problem is possible confusion amongst your peers. If you go too crazy adding categories on built-in classes, the people reading your code don’t know if a call they’re looking at is actually built-in or on. It can become harder to share code, too:
“Here, take this method. It’ll do what you want.”
NSData doesn’t have a
“Oh yeah, here’s the category for that”.
“Your NSData category is using an
“Sorry about that, here’s the
NSString category, as well as this one on
NSKeyedArchiver you’ll be needing”.
Category methods can also have name collisions. Apple has been known to extend their API, and they frequently use the “obvious” name for a new feature. Back in the olden days of Cocoa, around OS X 10.2 and prior, there was no “hidden” property on
NSViews. To hide a view you either had to move it out of the way or remove it from the view hierarchy. It was easy to add a category that added
NSView to make this work easier. Then OS X 10.3 added
setHidden: as an Apple-provided method, and suddenly things got confused. So what happened?
When your code gets loaded, whether at application launch time, via a plugin, or if you are loaded into another program like a preference pane or screen saver, your categories get loaded too. The Objective-C runtime adds your new methods to the existing classes at this time. If there’s already a method there, it gets replaced. So, if you had a category that included
setHidden: replaced Apple’s.
This kind of stuff can lead to subtle and difficult to track down bugs. The kind of embarrassing bugs where you file a Radar and then finally figure out it’s your fault after some back-and-forth with Apple’s engineers.
The solution to both of these problems is to prefix your category methods:
@interface NSString (BNRLolSpeek) - (NSString *) bnr_lolspeek; @end // BNRLolSpeek
This way, if for whatever reason Apple adds a
lolspeek method to
NSString, yours won’t clobber it. Also, someone reading code that uses
lolspeek can see immediately that a category method is being used and not get confused that they’re missing out on some Great Apple Functionality in their own code.
Categories are a very powerful, and cool feature of Objective-C, but as you saw here, they can cause problems in unexpected ways if you’re not careful.
(Edit: Dave DeLong brings up a good point that you could use suffixes instead of prefixes - you can use autocomplete without remembering that it’s a custom method, but it still has the “This is a category” tag on it.)