CodeToLive

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

  1. Breaking large classes into logical groups: Organize related methods together
  2. Adding framework functionality: Extend system classes with convenience methods
  3. Forward compatibility: Add methods to older SDK versions
  4. Mocking for testing: Override methods in test targets
  5. 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

Common Pitfalls

  1. Name collisions: Multiple categories adding methods with the same name
  2. Unexpected behavior: Overriding existing methods unintentionally
  3. Memory issues: With associated objects and swizzling
  4. Testing challenges: Some behaviors may be hard to test
  5. 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.

Next: Protocols & Delegation in Objective-C