Categories
Understand how Objective-C categories let you add methods to an existing class — including classes you do not own — without subclassing, and the rules and risks that come with that power.
A category is a way to add new methods to an existing Objective-C class without subclassing it and without even needing access to its original source code. Categories are how you can teach NSString, NSArray, or any of Apple's own framework classes a brand-new method, but that power comes with a short, strict list of rules and a real risk of naming collisions that every Objective-C developer needs to understand before reaching for one.
What a category is
A category is declared with the syntax @interface ClassName (CategoryName), followed by the methods being added, and a matching @implementation ClassName (CategoryName) providing their bodies. The CategoryName in parentheses is just a label for this particular grouping of additions — it does not create a subclass, a new type, or anything instances need to know about; it simply extends the existing class named before the parentheses.
Once a category's @implementation is compiled into your app (via any target that includes that file), every instance of that class — everywhere in the entire program, including instances that already existed before the category's file was ever imported — gains the new methods. This is categorically different (no pun intended) from subclassing, where only instances specifically created as the subclass gain the new behavior; a category modifies the one true class itself, at the level of the whole program.
This is what makes categories uniquely powerful for extending framework classes. You cannot subclass NSString in any practical way (its actual implementation is a private class cluster, and creating your own NSString instances the normal way returns one of Apple's internal concrete subclasses, not yours), but you absolutely can write a category that adds a method to it — which is exactly what the specification's example does.
Adding methods to a class you do not own
The specification's example adds email validation directly to NSString, a class defined by Apple in Foundation, which your code does not own and cannot modify the source of. A category sidesteps that limitation entirely: you declare the addition in your own header, implement it in your own .m file, and from that point on every NSString instance in the app — literal strings, strings read from disk, strings returned by any API — responds to isValidEmail.
This is the most common and most valuable use of categories: teaching a framework class one or two extra conveniences specific to your app's domain, without having to wrap every string in a custom class, without having to write a free function like BOOL StringIsValidEmail(NSString *s) that reads awkwardly at call sites, and without touching a single line of Apple's own code.
The hard rule: no new instance variables
Categories cannot add new instance variables to the class they extend. This is not a stylistic guideline — it is a hard compiler-enforced rule, and the reason is entirely about memory layout: by the time your category's file is compiled, the class's object layout (the fixed-size block of memory every instance of that class occupies, with each instance variable at a fixed byte offset) has already been determined by its original @interface. There is no room reserved for anything a category might want to add later, and no way to retroactively resize every already-allocated instance in memory.
If you try to declare an instance variable inside a category's @interface, the compiler rejects it outright. This trips up developers coming from languages where extension methods or partial classes can add state freely — in Objective-C, a category is method-only, full stop.
The standard workaround when a category genuinely needs to store additional per-instance data is associated objects, via objc_setAssociatedObject and objc_getAssociatedObject from <objc/runtime.h>. Associated objects attach a value to an existing object at runtime through a side table keyed by the object's identity and a unique key, rather than through the object's own memory layout — which is why they can be added after the fact, unlike a true instance variable. They are a deeper, more advanced runtime topic on their own, but it is worth knowing they exist as the standard escape hatch whenever a category needs storage a plain instance variable cannot provide.
Method collision risks
Because a category modifies the one true class for the entire program, two categories that happen to implement a method with the same name on the same class — or a category that happens to redefine a method the original class already implements — creates a genuine, dangerous collision. Objective-C has no compile-time error for this; the last implementation loaded into the runtime simply wins, silently, for every call site in the app, and which one loads last is determined by build and link order, which is often unpredictable and can even change between builds.
This is one of the most notorious hazards in Objective-C, precisely because it fails silently. If your category and a third-party library's category both add a method named isValidEmail to NSString with different logic, your app will compile cleanly, and depending on link order, either your validation logic or the library's will run everywhere isValidEmail is called — with no warning that anything is wrong. Worse, if a category happens to redefine a method the original class already implements internally, you can silently break the framework class's own behavior throughout the entire app.
This risk is exactly why the naming convention below exists, and why category method collisions are treated as a real, well-known hazard rather than a theoretical edge case — they have caused real, hard-to-diagnose production bugs, especially once an app links in more than a handful of third-party libraries, each of which may bring its own categories on common Foundation classes.
Naming conventions
The standard defense against collisions is prefixing every category method name with a short, project- or company-specific tag, exactly the way class names are traditionally prefixed in Objective-C (NS, UI, and so on, for Apple's own frameworks). Instead of adding isValidEmail directly, a well-behaved category would add xyz_isValidEmail, where xyz is a prefix specific to the project or library — making a collision with any other category astronomically unlikely, since two unrelated codebases choosing the exact same multi-character prefix and the exact same method name is a coincidence, not a near-certainty the way a bare, common name like isValidEmail is.
This convention costs a small amount of readability at the call site ([email xyz_isValidEmail] instead of [email isValidEmail]) in exchange for near-total protection against silent collisions, and it is considered a firm best practice, especially for any category that will ship inside a library or SDK consumed by other developers' codebases, where you have no control over and no visibility into what other categories might also be linked in.
Organizing categories into their own files
Categories are conventionally organized one per file, or grouped by concern, using a filename pattern of ClassName+CategoryName.h and ClassName+CategoryName.m — for example NSString+Validation.h and NSString+Validation.m for the specification's example. This immediately communicates, from the filename alone, which class is being extended and what the group of additions is for, and keeps unrelated additions (say, a NSString+Validation category and a separate NSString+Formatting category) cleanly separated into their own files rather than piled into one sprawling file of miscellaneous string helpers.
This organization also makes collisions easier to audit: if every category lives in its own clearly named file, a search across the codebase (or across linked libraries) for other files extending the same class is straightforward, and a code reviewer can immediately see, from the import list at the top of a file, exactly which categories are in play for any given class in that translation unit.
Code Examples
This is the interface declaration from the specification. It declares a new method, isValidEmail, on NSString, a class the declaring code does not own. The (Validation) label after NSString names this particular category — it does not create a new class or subclass. The matching @implementation NSString (Validation) block, in a separate file, would provide the actual regular-expression or character-by-character logic behind isValidEmail.
@interface NSString (Validation) extends the existing NSString class rather than declaring a new one; NSString itself is unchanged in name or identity.
The method is prefixed with eh_ (a project-specific tag) instead of a bare isValidEmail, following the naming convention that guards against collisions with any other category some other library might add to NSString. The file is named NSString+Validation.h/.m, immediately communicating which class is extended and what the addition is for. Expected result: Valid email - (BOOL)eh_isValidEmail {... } is the prefixed method name, reducing collision risk to near zero compared to a bare isValidEmail.
Common mistake: Naming the method bare isValidEmail instead of eh_isValidEmail risks a silent collision with any other category (from a third-party library, for instance) that also happens to add a method with that exact name to NSString.
Both libraries add a bare isValidEmail method to NSString with different logic. Neither the compiler nor the linker raises an error, and no warning is produced. Whichever implementation is loaded last into the Objective-C runtime silently wins for every call to isValidEmail across the entire app, which is precisely why bare, common method names are dangerous to add via category.
// Both compile cleanly. — the collision produces no diagnostic; it is only discoverable by noticing incorrect behavior at runtime.
Common mistake: Relying on a bare, common method name like isValidEmail in a category that might ship alongside other libraries is the exact scenario that leads to this kind of silent, hard-to-diagnose bug.
@interface NSString (Validation)
- (BOOL)isValidEmail;
@end// NSString+Validation.h
@interface NSString (Validation)
- (BOOL)eh_isValidEmail;
@end
// NSString+Validation.m
#import "NSString+Validation.h"
@implementation NSString (Validation)
- (BOOL)eh_isValidEmail {
NSString *pattern = @"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$";
NSPredicate *test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
return [test evaluateWithObject:self];
}
@end
// Usage anywhere in the app after importing the header:
NSString *email = @"[email protected]";
if ([email eh_isValidEmail]) {
NSLog(@"Valid email");
}// File A: NSString+ValidationA.m (from Library A)
@implementation NSString (ValidationA)
- (BOOL)isValidEmail {
return [self containsString:@"@"]; // naive check
}
@end
// File B: NSString+ValidationB.m (from Library B)
@implementation NSString (ValidationB)
- (BOOL)isValidEmail {
NSString *pattern = @"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$";
NSPredicate *test = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
return [test evaluateWithObject:self];
}
@end
// Both compile cleanly. Which isValidEmail actually runs
// at [someString isValidEmail] depends on link order,
// not on anything visible in the calling code.Category vs subclass: who gets the new method
A subclass's new behavior applies only to instances of that subclass; a category's new behavior applies to every instance of the extended class, program-wide.
Subclassing: Category:
NSObject NSString (Validation)
| |
MyString : NSString adds isValidEmail to
| EVERY NSString instance
only MyString instances in the whole program,
get new methods including ones that
already existedImportant Rules
A category adds methods to an existing class using @interface ClassName (CategoryName) / @implementation ClassName (CategoryName), without creating a subclass.
Every instance of the extended class gains the category's methods program-wide, including instances created before the category was linked in.
Categories cannot add new instance variables — the class's memory layout is already fixed by the time a category is compiled; associated objects are the standard workaround when storage is needed.
Two categories (or a category and the original class) defining the same method name causes a silent collision resolved by unpredictable link order, with no compiler error or warning.
Prefixing category method names (e.g. eh_isValidEmail) is the standard defense against collisions, especially for categories shipped in a library.
Categories are conventionally organized as ClassName+CategoryName.h/.m, one file per category, grouped by concern.
Where this shows up
Categories are how countless Objective-C libraries add small conveniences to Foundation and UIKit classes — string trimming and validation helpers on NSString, color-from-hex initializers on UIColor, safe bounds-checked accessors on NSArray. They are also how Apple itself splits large classes like NSString across multiple files internally (NSString+Path handling extensions, for instance), and how you divide your own big classes into logically separate files without changing their public identity.
@interface NSString (Validation)\n@property (nonatomic, strong) NSString *cachedResult;\n@end — attempting to add an instance variable/property with storage via a category.
Categories cannot declare instance variables. If per-instance storage is genuinely needed, use objc_setAssociatedObject / objc_getAssociatedObject from <objc/runtime.h>, or reconsider whether a subclass or wrapper object is more appropriate.
The class's memory layout is fixed at the point its original @interface was compiled; a category compiled later has no way to reserve additional space in every already-existing instance.
Adding a bare, common method name like isValidEmail directly to a framework class like NSString in a category meant to ship in a shared library.
Prefix the method name, e.g. eh_isValidEmail, so it is extremely unlikely to collide with a method some other category or library also adds to the same class.
Without a prefix, any other category defining the same method name on the same class causes a silent, link-order-dependent collision with no compiler warning.
Assuming that because your category's @implementation compiles without error, its method is definitely the one that will run at every call site.
Recognize that a successful compile says nothing about collisions — only checking for other categories on the same class (by name/prefix or via a project-wide search) rules that out.
The compiler and linker do not detect or warn about duplicate category method implementations; the last one loaded into the runtime silently wins everywhere.
Piling every unrelated NSString helper method into a single giant NSString+Utilities.h/.m file.
Organize categories by concern into separate files, e.g. NSString+Validation.h/.m and NSString+Formatting.h/.m, each focused on one purpose.
A single sprawling category file makes it harder to see, at a glance, what each addition is for, and makes potential naming collisions with other libraries' categories harder to audit.
What does a category add to an existing class? A. New instance variables only B. A completely new subclass C. New methods, without subclassing and without new instance variables D. A new memory layout for existing instances
C. New methods, without subclassing and without new instance variables — Categories add methods to an existing class program-wide; they cannot add instance variables and they do not create a subclass.
Why can't a category add a new instance variable to the class it extends? A. Because Objective-C categories are read-only B. Because the class's memory layout is already fixed by the time the category is compiled, leaving no reserved space for new variables C. Because instance variables can only be added in a .h file, never a .m file D. Because only NSObject subclasses support instance variables
B. Because the class's memory layout is already fixed by the time the category is compiled, leaving no reserved space for new variables — By the time a category compiles, the extended class's fixed-offset memory layout has already been determined by its original @interface, so there is no room to add storage for every existing and future instance.
Two categories both add a bare method named isValidEmail to NSString with different logic. What happens? A. The compiler refuses to build the project B. Both implementations run every time isValidEmail is called C. The build succeeds, and whichever implementation is loaded last (determined by link order) silently wins everywhere, with no warning D. Objective-C automatically merges the two implementations into one
C. The build succeeds, and whichever implementation is loaded last (determined by link order) silently wins everywhere, with no warning — Category method name collisions produce no compile-time or link-time error; the runtime simply uses whichever implementation was loaded last, which is why they are a real and dangerous hazard.
What is the standard naming convention used to reduce the risk of category method collisions? A. Always name category methods identically to well-known framework methods B. Prefix category method names with a short project- or library-specific tag, e.g. eh_isValidEmail C. Never give category methods any arguments D. Declare category methods as @optional
B. Prefix category method names with a short project- or library-specific tag, e.g. eh_isValidEmail — Prefixing category method names makes an accidental name collision with another category on the same class extremely unlikely, which is why it is the standard defense, especially for categories shipped in shared libraries.
What is the conventional file naming pattern for organizing a category? A. CategoryOnly.h and CategoryOnly.m with no reference to the extended class B. ClassName+CategoryName.h and ClassName+CategoryName.m C. Category_ClassName_1.h with no matching .m file D. All categories must live inside the original class's own .m file
B. ClassName+CategoryName.h and ClassName+CategoryName.m — The standard pattern, e.g. NSString+Validation.h/.m, names both the class being extended and the purpose of the category, making the file's intent clear at a glance and keeping unrelated additions separated.
Write a category on NSArray named SafeAccess that adds a method eh_objectOrNilAtIndex: returning the object at a given index, or nil (instead of raising an out-of-bounds exception) if the index is beyond the array's count. Organize it as NSArray+SafeAccess.h/.m in your answer, and explain in a comment why the method is prefixed.
// NSArray+SafeAccess.h @interface NSArray (SafeAccess) - (id)eh_objectOrNilAtIndex:(NSUInteger)index; @end // NSArray+SafeAccess.m #import "NSArray+SafeAccess.h" @implementation NSArray (SafeAccess) // Prefixed with eh_ to avoid colliding with a same-named // method some other category or library might add to NSArray. - (id)eh_objectOrNilAtIndex:(NSUInteger)index { if (index >= self.count) { return nil; } return [self objectAtIndex:index]; } @end
Compare the requested index against self.count before calling objectAtIndex: to avoid NSArray's usual out-of-bounds exception.