Categories & Extensions in Objective-C
Categories and extensions are powerful Objective-C features that allow you to extend existing classes without subclassing.
Categories allow you to add methods to existing classes, while extensions let you declare additional methods and properties that must be implemented in the main implementation block.
Categories vs. Extensions
Feature | Categories | Extensions |
---|---|---|
Declaration | Separate interface/implementation files | In main implementation file |
Adding methods | Yes | Yes |
Adding properties | Only with associated objects | Yes |
Adding instance variables | No | No |
Visibility | Public | Private |
Creating Categories
// NSString+Utilities.h
#import <Foundation/Foundation.h>
@interface NSString (Utilities)
- (BOOL)isPalindrome;
- (NSString *)reversedString;
+ (NSString *)randomStringOfLength:(NSUInteger)length;
@end
// NSString+Utilities.m
#import "NSString+Utilities.h"
@implementation NSString (Utilities)
- (BOOL)isPalindrome {
NSString *lowercase = [self lowercaseString];
NSUInteger length = [lowercase length];
for (NSUInteger i = 0; i < length/2; i++) {
if ([lowercase characterAtIndex:i] !=
[lowercase characterAtIndex:length - 1 - i]) {
return NO;
}
}
return YES;
}
- (NSString *)reversedString {
NSMutableString *reversed = [NSMutableString string];
NSInteger i = [self length] - 1;
while (i >= 0) {
[reversed appendFormat:@"%C", [self characterAtIndex:i]];
i--;
}
return reversed;
}
+ (NSString *)randomStringOfLength:(NSUInteger)length {
NSString *letters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
NSMutableString *randomString = [NSMutableString stringWithCapacity:length];
for (int i = 0; i < length; i++) {
[randomString appendFormat:@"%C", [letters characterAtIndex:arc4random_uniform((uint32_t)[letters length])]];
}
return randomString;
}
@end
Using the Category
#import "NSString+Utilities.h"
NSString *test = @"racecar";
if ([test isPalindrome]) {
NSLog(@"'%@' is a palindrome!", test);
}
NSString *reversed = [test reversedString];
NSString *random = [NSString randomStringOfLength:10];
Note: Category methods become available to all instances of the class, including those created before the category was defined.
Adding Properties with Associated Objects
// UIView+CustomProperties.h
#import <UIKit/UIKit.h>
@interface UIView (CustomProperties)
@property (nonatomic, strong) NSNumber *userTag;
@end
// UIView+CustomProperties.m
#import "UIView+CustomProperties.h"
#import <objc/runtime.h>
@implementation UIView (CustomProperties)
static char kUserTagKey;
- (NSNumber *)userTag {
return objc_getAssociatedObject(self, &kUserTagKey);
}
- (void)setUserTag:(NSNumber *)userTag {
objc_setAssociatedObject(self, &kUserTagKey, userTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
Warning: Associated objects don't provide true property synthesis and should be used sparingly. They're not type-safe and can lead to memory issues if not managed properly.
Class Extensions
// Person.h (public interface)
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
- (void)introduce;
@end
// Person.m (with extension)
#import "Person.h"
// Class extension (private interface)
@interface Person ()
@property (assign, nonatomic) NSInteger age;
- (void)privateMethod;
@end
@implementation Person
- (void)introduce {
NSLog(@"My name is %@", self.name);
[self privateMethod];
}
- (void)privateMethod {
NSLog(@"This is private");
}
@end
Practical Uses of Categories
- Breaking large classes into logical groups: Organize related methods together
- Adding framework functionality: Extend system classes with convenience methods
- Forward compatibility: Add methods to older SDK versions
- Mocking for testing: Override methods in test targets
- Third-party library integration: Add integration points without modifying original code
Method Swizzling
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(tracking_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)tracking_viewWillAppear:(BOOL)animated {
[self tracking_viewWillAppear:animated];
NSLog(@"ViewWillAppear: %@", NSStringFromClass([self class]));
}
@end
Important: Method swizzling is powerful but dangerous. Use it only when absolutely necessary and document it thoroughly.
Best Practices
- Prefix category method names to avoid collisions (e.g.,
abc_myMethod
) - Document your categories thoroughly
- Avoid overriding existing methods (use swizzling carefully if you must)
- Keep related functionality together
- Use extensions for private methods and properties
- Be cautious with associated objects - they can lead to memory leaks
- Consider using protocols instead when appropriate
Common Pitfalls
- Name collisions: Multiple categories adding methods with the same name
- Unexpected behavior: Overriding existing methods unintentionally
- Memory issues: With associated objects and swizzling
- Testing challenges: Some behaviors may be hard to test
- Debugging complexity: Methods appear to come from nowhere
Note: While categories are powerful, sometimes subclassing or composition may be a better solution depending on your needs.