Let’s say we are building an app that needs a special sort order for strings.
We wish we had a method on every NSString
that we could reference as a
comparator. Thanks to the magic of Objective-C categories we can
do this:
// In our .m file
@implementation NSString (WithOurStandardCompare)
- (NSComparisonResult)localizedStandardCompare:(NSString *)string
{
//... Do the magic here
return comparisonResult;
}
@end
Now, an array of NSStrings
can be sorted using our category method like so:
NSArray *sorted = nil;
sorted = [sorted sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
In iOS 4.0, Apple added a method on NSString
with the exact same name as
the one in our fictional category. Whoops. Our method named
localizedStandardCompare:
is used in place of Apple’s everywhere in our
application’s runtime since it’s mixed in as a category after launch.
This works fine for our code that expects our “standard” comparison behavior.
But Apple’s new method sorts based on display rules from the
Finder. If localizedStandardCompare:
is used by any other
Apple code expecting the Finder sort behavior, they will get ours instead. You
might end up filing a radar report with Apple thinking it’s a fault in their
frameworks! Tracking this bug down will take a long, long time.
This illustration may seem a bit contrived, but the problem is very real. If you make use of the sweet libraries available on Cocoa Controls or Github, you could easily find yourself in the middle of a method name collision mess. There’s a lot of good intentions and convenience when using categories, but you must take steps to keep these collisions from happening; especially if you are developing one of these libraries for public reuse!
After talking with a few seasoned devs, I’ve adopted a prefix convention to namespace methods in categories for classes I don’t control. For instance:
// In our .m file
@implementation NSString (WithOurStandardCompare)
- (NSComparisonResult)cm_localizedStandardCompare:(NSString *)string
{
//... Do the magic here
return comparisonResult;
}
@end
In this case, I put “cm_” at the beginning of the method name to signify Cocoa Manifest. It’d be an unfortunate miracle if Apple saw the need to prefix their method the same way. And prefixing with initials reasonably defends against collision with other libraries.
Resorting to name prefixes is the norm in Objective-C. Alas, we are stuck with two letter codes like “NS”, “CA”, and our initials to keep these collisions from happening. I’d love to have a better namespace mechanism in the language proper (or use other languages with better idioms—*cough* MacRuby *cough*), but this is what we’re stuck with for now.
If you release libraries in Objective-C with categories on system classes, please, please prefix your method names. I’ve been burned by enough collisions in libraries already.
✦ PermalinkMy books...