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, 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,NSArrayorUIViewwithout 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
NSObjectdeclaring optional methods — now superseded by@optionalin 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 |
|
|
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 |
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
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
superfor 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
-
Properties and Encapsulation — the
readonly-public/readwrite-private idiom that class extensions enable. -
Protocols and Delegation — the formal successor to informal protocols.
-
The Objective-C Runtime — method swizzling and the runtime API behind associated objects.
-
Inheritance and Polymorphism — when a subclass is the better answer.