- Published on
> Categories vs Class Extensions in Objective-C
- Authors

- Name
- Mick MacCallum
- @0x7fs
Objective-C offers two ways to add methods to a class without subclassing: categories and class extensions. They look similar but have fundamentally different use cases. Understanding the distinction will clean up your code organization.
Categories: Adding Methods to Any Class
A category adds methods to an existing class—even one you don't have the source code for:
// NSString+Validation.h
@interface NSString (Validation)
- (BOOL)isValidEmail;
- (BOOL)isNumeric;
@end
// NSString+Validation.m
@implementation NSString (Validation)
- (BOOL)isValidEmail {
NSString *pattern = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
return [predicate evaluateWithObject:self];
}
- (BOOL)isNumeric {
NSScanner *scanner = [NSScanner scannerWithString:self];
[scanner scanDouble:nil];
return [scanner isAtEnd];
}
@end
Now every NSString in your app responds to isValidEmail and isNumeric.
Category Limitations
Categories can't add instance variables. This fails:
@interface NSString (Validation)
@property (nonatomic) BOOL wasValidated; // Won't work
@end
You can declare the property, but there's no backing storage. The runtime doesn't reserve space in existing NSString instances for your new variable.
The workaround uses associated objects:
#import <objc/runtime.h>
static const void *WasValidatedKey = &WasValidatedKey;
@implementation NSString (Validation)
- (BOOL)wasValidated {
return [objc_getAssociatedObject(self, WasValidatedKey) boolValue];
}
- (void)setWasValidated:(BOOL)validated {
objc_setAssociatedObject(self, WasValidatedKey, @(validated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
It works, but it's clunky. If you need instance variables, consider other approaches.
Class Extensions: Private Interface
A class extension looks like a category with no name:
// Person.m (not the header)
@interface Person ()
@property (nonatomic, copy) NSString *socialSecurityNumber;
- (void)recalculateTaxBracket;
@end
Unlike categories, extensions:
- Must be in the same compilation unit as the class implementation
- Can add instance variables
- Declare private interface
The compiler knows about extension properties at compile time, so it synthesizes ivars normally.
When to Use Each
Use categories when:
- Adding methods to classes you don't control (Foundation, UIKit, third-party)
- Organizing a large class into logical groups
- Making methods available across your entire project
Use class extensions when:
- Declaring private properties with real storage
- Hiding internal methods from your public header
- Extending your own classes with implementation details
Organizing With Categories
Large classes benefit from splitting implementation across files:
// Person.h - main interface
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
// Person+Networking.h
@interface Person (Networking)
- (void)fetchProfileFromServer;
- (void)uploadChanges;
@end
// Person+Formatting.h
@interface Person (Formatting)
- (NSString *)formattedAddress;
- (NSAttributedString *)displayName;
@end
Each category gets its own .h and .m file. The class stays manageable even as it grows.
The Conflict Risk
If two categories define the same method, behavior is undefined:
// StringHelpers.m
@implementation NSString (Helpers)
- (NSString *)trimmed {
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
@end
// StringUtils.m (different file, different category)
@implementation NSString (Utils)
- (NSString *)trimmed { // Collision!
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
@end
One implementation wins; which one is unpredictable. Prefix your category method names:
- (NSString *)acm_trimmed;
Extensions for Protocol Conformance
A common pattern declares private protocol conformance in an extension:
// ViewController.m
@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) NSArray *items;
@end
The header doesn't expose these protocols as public API, but the implementation conforms to them. Clean separation of public and private interface.
Testing Considerations
Private methods in extensions aren't visible to test files. You can either:
- Test through public interface (preferred)
- Declare a testing category that exposes private methods
- Put the extension in a private header and import it in tests
// Person+Private.h (shared with tests)
@interface Person ()
- (void)recalculateTaxBracket;
@end
The Practical Summary
Categories add behavior to classes you don't own. Extensions add private interface to classes you do own. Categories can't add storage; extensions can. Both are essential tools for organizing Objective-C code.
Use categories to extend Foundation and UIKit with project-specific conveniences. Use extensions to keep your headers clean and your implementation details hidden.
// Continue_Learning
Blocks - Closures Before Swift Existed
Before Swift closures, Objective-C had blocks. The syntax is different, but the power is the same—here's how to use them effectively.
The NSObject Class Hierarchy Explained
Every Objective-C class inherits from NSObject (usually). Here's what NSObject provides and why it matters for every object you create.
Message Passing - The Heart of Objective-C
Unlike C++ virtual methods, Objective-C uses dynamic message passing. Understanding this mechanism unlocks the language's full power.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.