Categories and Extensions

This section documents modern Objective-C as implemented by Apple Clang in the current Xcode release — Objective-C 2.0 plus ARC, literals and subscripting, instancetype, Clang modules, lightweight generics, nullability and @available — as published at Apple Developer Documentation and the Clang Objective-C specifications, which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against developer.apple.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A category adds methods to an existing class — including one whose source you do not have — without subclassing it. A class extension is a special unnamed category that declares the private half of a class you are implementing. They share a syntax and are frequently confused, but they solve different problems and have very different rules.

Categories

// NSString+MyValidation.h
#import <Foundation/Foundation.h>

@interface NSString (MyValidation)
- (BOOL)my_isValidEmailAddress;
- (NSString *)my_trimmedString;
@end
// NSString+MyValidation.m
#import "NSString+MyValidation.h"

@implementation NSString (MyValidation)

- (BOOL)my_isValidEmailAddress {
    NSString *pattern = @"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";
    NSPredicate *test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
    return [test evaluateWithObject:self];
}

- (NSString *)my_trimmedString {
    return [self stringByTrimmingCharactersInSet:
                [NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

@end

Any client that imports the category header can now send those messages to any NSString anywhere in the process — including instances created by Apple’s own frameworks, since the methods are added to the class itself at load time.

The conventional file name is Class+CategoryName.h / .m, and the category name describes the added concern.

What Categories Are For

  • Extending classes you do not own. The canonical use — adding a convenience method to NSString, NSArray or UIView without a subclass that everybody would then have to remember to use.

  • Splitting a large class across files. A big class can be implemented as a core plus several categories (MyViewController+TableView.m, MyViewController+Networking.m), each with its own focused header.

  • Declaring informal protocols. A historical pattern — a category on NSObject declaring optional methods — now superseded by @optional in a real @protocol.

  • Grouping related methods in headers for documentation purposes, even within your own class.

Class Extensions

A class extension is written with empty parentheses and must appear in the same compilation unit as the class’s @implementation — in practice, at the top of the .m file:

// Download.m
#import "Download.h"

@interface Download ()  <NSURLSessionDelegate>          // can adopt protocols privately

@property (nonatomic, strong) NSURLSession *session;    // private property
@property (nonatomic, readwrite) double progress;       // public readonly, private readwrite

- (void)beginNextChunk;                                 // private method declaration

@end

@implementation Download
// … implements both the public interface and the extension
@end

This is the mechanism for privacy in Objective-C, and the differences from a category are what make it work:

Class extension Category

Syntax

@interface Foo ()

@interface Foo (Name)

Can add ivars

Yes

No

Can add properties with synthesised storage

Yes

No (declaration only)

Must be compiled with the class

Yes

No

Methods it declares

Must be implemented in the main @implementation (compiler-checked)

May be implemented anywhere

Typical use

The private interface of your own class

Extending someone else’s class

Because the extension is compiled together with the class, the compiler does check that every method it declares is implemented — a category gives you no such guarantee.

Associated Objects

Categories cannot add instance variables, because the ivar layout is fixed when the class is compiled. The run time offers a supported workaround: attach a value to an object under a key, stored in a side table.

#import <objc/runtime.h>

@interface UIView (MyTooltip)
@property (nonatomic, copy) NSString *my_tooltip;
@end

@implementation UIView (MyTooltip)

// A unique key: the address of a static variable is guaranteed distinct.
static const void *MyTooltipKey = &MyTooltipKey;

- (NSString *)my_tooltip {
    return objc_getAssociatedObject(self, MyTooltipKey);
}

- (void)setMy_tooltip:(NSString *)tooltip {
    objc_setAssociatedObject(self, MyTooltipKey, tooltip, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

Note the @property in the category: it declares the accessors, but no storage is synthesised — you must write both accessors yourself, which is exactly what the code above does.

The association policy mirrors the property attributes:

Policy Equivalent to

OBJC_ASSOCIATION_ASSIGN

assign / unsafe_unretained (not zeroed — can dangle)

OBJC_ASSOCIATION_RETAIN_NONATOMIC

nonatomic, strong

OBJC_ASSOCIATION_COPY_NONATOMIC

nonatomic, copy

OBJC_ASSOCIATION_RETAIN

atomic, strong

OBJC_ASSOCIATION_COPY

atomic, copy

Associated objects are released automatically when the host object is deallocated; objc_removeAssociatedObjects clears all of them and should essentially never be called on an object you do not own. There is no weak policy — OBJC_ASSOCIATION_ASSIGN does not zero.

Use them sparingly. They are invisible in the class’s declaration, cost a hash-table lookup per access, and make object state hard to follow. A subclass, a wrapper, or a dictionary keyed by the object is usually clearer.

Category Pitfalls

Categories are powerful precisely because they modify a class globally, for the whole process. That is also what makes them dangerous.

No Instance Variables

A category cannot add ivars or synthesised properties. Declaring a @property in a category compiles but generates nothing — you get a "property requires method … to be defined" warning at implementation time and an unrecognized selector crash at run time if you ignore it. Use associated objects, or reconsider the design.

Name Collisions

If a category method has the same selector as an existing method, the category silently wins. Whichever category is loaded last wins among several, and load order is not defined. There is no warning, no error, and no diagnostic at the point of failure — you have simply replaced a framework method for the entire process.

The defence is a prefix on every category method:

// Do NOT do this -- silently replaces or collides with Foundation's own method:
@interface NSString (Bad)
- (NSString *)trimmed;
@end

// Do this -- collision-proof by construction:
@interface NSString (MyApp)
- (NSString *)myapp_trimmed;
@end

The convention is a short lowercase prefix plus an underscore (my_, abc_), matching your class prefix. In a shipped library it is not optional: an unprefixed category method in a framework can break an unrelated application that adds a method of the same name.

Overriding Is Undefined

A category may override an existing method, but you should treat it as unsupported:

  • Which implementation wins between two categories is undefined.

  • The original implementation becomes unreachable — there is no super for a category, because the category is not a subclass. Calling [super foo] from a category calls the superclass’s method, not the one you replaced.

  • The override applies process-wide, including inside framework code you did not write.

To modify existing behaviour safely, subclass; where subclassing is impossible, method swizzling at least preserves the original implementation so it can be called — see The Objective-C Runtime, which also explains why swizzling is a last resort.

+load and +initialize

A category may implement +load, and unlike other methods it does not replace the class’s own +load — every +load runs. +initialize, by contrast, follows the normal collision rules and a category implementing it does replace the class’s. Avoid both in categories unless you are certain.

Linker Flags

Categories in a static library are a special case: because a category adds no new symbol of its own, the linker may drop the object file entirely, and calls crash with unrecognized selector at run time. Link with -ObjC (or -all_load) to force every Objective-C object file in. See Build and Tooling.

Posing: Removed Legacy

+poseAsClass: let a class replace another class throughout the run time — every subsequent message to the target class went to the poser instead.

// HISTORICAL ONLY -- does not compile against any current SDK.
@interface MyString : NSString
@end

@implementation MyString
+ (void)load {
    [MyString poseAsClass:[NSString class]];   // removed
}
@end

It was deprecated in Mac OS X 10.5 and removed entirely in the modern (64-bit) run time; it does not exist on iOS at all. It is documented here only so that you can recognise it in old code, alongside Objective-C garbage collection, deprecated in OS X 10.8 and removed in 10.11. Neither is available in any current toolchain.

Where posing was once used, today’s answers are: a category (to add), a subclass (to specialise), method swizzling (to intercept, keeping the original callable), or dependency injection (to substitute, and by far the most maintainable).

See Also