Extending Classes Without Subclassing: A Practical Look at Objective-C Categories
Objective-C categories let you add methods to classes you don't own — no subclassing required. Here's how to use them safely, including prefixes, the no-ivars limit, and associated objects.
11 Mar 2026, 09:56 UTC

You need a small helper on NSString — say, trimming whitespace and collapsing internal runs of spaces — and you need it in a dozen places across an app. Subclassing NSString is a dead end: it's a class cluster, so your subclass never actually receives the strings produced by frameworks. The tool built for exactly this situation is the category, and it's worth understanding both what it does well and where it quietly bites.
What a category actually does
A category adds methods to an existing class at runtime, without inheritance and without access to the original source code. You declare it in a header and implement it like any other class extension:
// NSString+Whitespace.h
#import <Foundation/Foundation.h>
@interface NSString (Whitespace)
- (NSString *)rmf_stringByCollapsingWhitespace;
@end
// NSString+Whitespace.m
#import "NSString+Whitespace.h"
@implementation NSString (Whitespace)
- (NSString *)rmf_stringByCollapsingWhitespace {
NSString *trimmed = [self stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSArray *parts = [trimmed componentsSeparatedByCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSPredicate *notEmpty = [NSPredicate predicateWithFormat:@"length > 0"];
return [[parts filteredArrayUsingPredicate:notEmpty] componentsJoinedByString:@" "];
}
@endOnce the header is imported, every NSString in your process responds to the new method — including strings created by frameworks you don't control. No wrapper objects, no subclassing gymnastics. This works equally well for organizing your own large classes: splitting a view controller's networking, table-view data source, and formatting code into separate category files keeps each file focused while the compiler still sees one class.
The prefix habit that saves you later
Because category methods merge into the class's real method table, a name collision is silent and nasty. If Apple adds stringByCollapsingWhitespace to NSString in a future SDK, or a third-party library defines the same method, which implementation runs is undefined. The standard defense is an ugly-but-cheap prefix on every category method you add to classes you don't own — rmf_ above, or your own three-letter convention. It feels excessive until the first time it isn't.
The hard limit: no new storage
Categories cannot add instance variables. A class's memory layout is fixed when it's compiled, and categories are applied afterward, so there's no slot to put new data in. Declaring an @property in a category is legal syntax, but the compiler won't synthesize a backing ivar — you get accessors you must implement yourself, and nothing to store the value in.
The usual escape hatch is associated objects, a runtime API that attaches key-value storage to any object from the outside:
#import <objc/runtime.h> static const void *kCacheKey = &kCacheKey; - (void)setRmf_cachedValue:(NSString *)value { objc_setAssociatedObject(self, kCacheKey, value, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)rmf_cachedValue { return objc_getAssociatedObject(self, kCacheKey); }This genuinely works — the associated value is released when the host object deallocates, per the association policy you choose — but treat it as a last resort. It hides state from the debugger's view of the object, adds a small runtime lookup cost, and makes the object's behavior harder to reason about. If you find yourself bolting several associated properties onto a class, that's usually a sign you wanted a wrapper class or a subclass of something you own instead.
Overriding existing methods: just don't
A category can provide a new implementation of a method the class already has, and yours will win — which also means the original implementation becomes unreachable (there's no
superequivalent to call it). Worse, if two categories on the same class define the same method, which one loads is non-deterministic. This is a reliable source of "works on my machine" bugs, especially when the conflicting category arrives via a dependency. If you need to intercept existing behavior, method swizzling is at least explicit about it; silent overriding in a category is how you get crashes nobody can reproduce.A quick way to verify your setup
After adding a category, three cheap checks confirm it's wired correctly: first, confirm the method appears in autocomplete on a plain
NSStringvariable in a file that imports the header. Second, call it on a string literal ([@" a b " rmf_stringByCollapsingWhitespace]should return@"a b") in a unit test. Third — and this one matters for larger apps — if your category lives in a static library and the linker strips it, calls will fail at runtime with "unrecognized selector sent to instance"; adding-ObjCto Other Linker Flags in the app target's build settings fixes that. That linker flag is the single most common reason a perfectly correct category "doesn't work" in a real project.The takeaway
Reach for a category when you're adding behavior, not state: utility methods on system classes, or organizational splits of your own classes. Prefix methods on classes you don't own, never override existing methods, and treat associated objects as a sign to reconsider the design. Used that way, categories are one of Objective-C's genuinely pleasant features — the language's answer to "I just need this class to do one more thing."
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.