| name | Objective-C Protocols and Categories |
| user-invocable | false |
| description | Use when objective-C protocols for defining interfaces and categories for extending classes, including formal protocols, optional methods, class extensions, and patterns for modular, reusable code design. |
| allowed-tools | [] |
Objective-C Protocols and Categories
Introduction
Protocols and categories are fundamental Objective-C features for defining
interfaces and extending behavior. Protocols declare method contracts that
classes can adopt, enabling polymorphism and delegation patterns. Categories
extend existing classes with new methods without subclassing or source access.
Protocols serve similar purposes to Java interfaces or Swift protocols, enabling
multiple inheritance of behavior through composition. Categories are unique to
Objective-C, allowing developers to organize code, add functionality to system
classes, and split large implementations across multiple files.
This skill covers formal protocols, optional methods, protocol composition,
categories, class extensions, and best practices for modular Objective-C design.
Formal Protocols
Formal protocols declare method and property requirements that adopting classes
must implement, establishing contracts for polymorphic behavior.
@protocol Drawable <NSObject>
@required
- (void)draw;
- (CGRect)bounds;
@optional
- (void)drawWithStyle:(NSString *)style;
@end
@interface Circle : NSObject <Drawable>
@property (nonatomic, assign) CGFloat radius;
@property (nonatomic, assign) CGPoint center;
@end
@implementation Circle
- (void)draw {
NSLog(@"Drawing circle at (%.1f, %.1f) with radius %.1f",
self.center.x, self.center.y, self.radius);
}
- (CGRect)bounds {
return CGRectMake(
self.center.x - self.radius,
self.center.y - self.radius,
self.radius * 2,
self.radius * 2
);
}
@end
@protocol Movable <NSObject>
- (void)moveToPoint:(CGPoint)point;
- (CGPoint)currentPosition;
@end
@protocol Scalable <NSObject>
- (void)scaleBy:(CGFloat)factor;
- (CGFloat)currentScale;
@end
@interface Shape : NSObject <Drawable, Movable, Scalable>
@property (nonatomic, assign) CGPoint position;
@property (nonatomic, assign) CGFloat scale;
@end
@implementation Shape
- (void)draw {
NSLog(@"Drawing shape");
}
- (CGRect)bounds {
return CGRectZero;
}
- (void)moveToPoint:(CGPoint)point {
self.position = point;
}
- (CGPoint)currentPosition {
return self.position;
}
- (void)scaleBy:(CGFloat)factor {
self.scale *= factor;
}
- (CGFloat)currentScale {
return self.scale;
}
@end
void drawShapes(NSArray<id<Drawable>> *shapes) {
for (id<Drawable> shape in shapes) {
[shape draw];
NSLog(@"Bounds: %@", NSStringFromCGRect([shape bounds]));
}
}
void checkConformance(id object) {
if ([object conformsToProtocol:@protocol(Drawable)]) {
id<Drawable> drawable = object;
[drawable draw];
}
}
@protocol AdvancedDrawable <Drawable>
- (void)drawWithTransform:(CGAffineTransform)transform;
- (void)drawWithBlendMode:(CGBlendMode)blendMode;
@end
@interface AdvancedShape : NSObject <AdvancedDrawable>
@end
@implementation AdvancedShape
- (void)draw {
NSLog(@"Advanced drawing");
}
- (CGRect)bounds {
return CGRectZero;
}
- (void)drawWithTransform:(CGAffineTransform)transform {
NSLog(@"Drawing with transform");
}
- (void)drawWithBlendMode:(CGBlendMode)blendMode {
NSLog(@"Drawing with blend mode");
}
@end
Protocols enable polymorphic code that works with any object implementing the
required methods, regardless of class hierarchy.
Optional Protocol Methods
Optional protocol methods allow adopters to implement only relevant methods,
with runtime checking for implementation before calling.
@protocol DataSourceDelegate <NSObject>
@required
- (NSInteger)numberOfItems;
@optional
- (NSString *)titleForItemAtIndex:(NSInteger)index;
- (UIImage *)imageForItemAtIndex:(NSInteger)index;
- (void)didSelectItemAtIndex:(NSInteger)index;
@end
@interface ListView : UIView <DataSourceDelegate>
@property (nonatomic, weak) id<DataSourceDelegate> dataSource;
@end
@implementation ListView
- (void)reloadData {
NSInteger count = [self.dataSource numberOfItems];
for (NSInteger i = 0; i < count; i++) {
if ([self.dataSource respondsToSelector:
@selector(titleForItemAtIndex:)]) {
NSString *title = [self.dataSource titleForItemAtIndex:i];
NSLog(@"Title: %@", title);
}
if ([self.dataSource respondsToSelector:
@selector(imageForItemAtIndex:)]) {
UIImage *image = [.dataSource imageForItemAtIndex:i];
(, image);
}
}
}
- ()numberOfItems {
;
}
- ()numberOfItems {
;
}
- ( *)titleForItemAtIndex:()index {
[ stringWithFormat:, ()index];
}
- ()viewControllerWillAppear:( *)controller;
- ()viewControllerDidAppear:( *)controller;
- ()viewControllerWillDisappear:( *)controller;
- ()viewControllerDidDisappear:( *)controller;
(, ) <ViewControllerDelegate> delegate;
- ()viewWillAppear:()animated {
[ viewWillAppear:animated];
([.delegate respondsToSelector:
(viewControllerWillAppear:)]) {
[.delegate viewControllerWillAppear:];
}
}
- ()viewDidAppear:()animated {
[ viewDidAppear:animated];
([.delegate respondsToSelector:(viewControllerDidAppear:)]) {
[.delegate viewControllerDidAppear:];
}
}
(, ) *identifier;
(, ) *name;
(, ) *metadata;
identifier = _identifier;
name = _name;
Always check for optional method implementation with respondsToSelector:
before calling to prevent crashes from unimplemented methods.
Categories for Class Extension
Categories add methods to existing classes without subclassing, enabling code
organization and extension of system classes.
@interface NSString (Validation)
- (BOOL)isValidEmail;
- (BOOL)isValidPhoneNumber;
- (NSString *)trimmedString;
@end
@implementation NSString (Validation)
- (BOOL)isValidEmail {
NSString *emailRegex =
@"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}";
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"SELF MATCHES %@", emailRegex];
return [predicate evaluateWithObject:self];
}
- (BOOL)isValidPhoneNumber {
NSString *phoneRegex = @"^\\d{3}-\\d{3}-\\d{4}$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"SELF MATCHES %@", phoneRegex];
return [predicate evaluateWithObject:self];
}
- (NSString *)trimmedString {
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
@end
void categoryExample(void) {
NSString *email = @"user@example.com";
if ([email isValidEmail]) {
NSLog(@"Valid email");
}
NSString *text = @" Hello World ";
NSString *trimmed = [text trimmedString];
(, trimmed);
}
- ()saveData:( *)data;
- ( *)loadData;
- ()syncToServer;
- ()downloadFromServer;
- ()saveToUserDefaults:( *)data;
- ( *)loadFromUserDefaults;
- ()saveData:( *)data {
();
}
- ( *)loadData {
[ data];
}
- ()syncToServer {
();
}
- ()downloadFromServer {
();
}
- ()saveToUserDefaults:( *)data {
[[ standardUserDefaults] setObject:data forKey:];
}
- ( *)loadFromUserDefaults {
[[ standardUserDefaults] dictionaryForKey:];
}
+ ( *)brandPrimaryColor;
+ ( *)brandSecondaryColor;
+ ( *)brandPrimaryColor {
[ colorWithRed: green: blue: alpha:];
}
+ ( *)brandSecondaryColor {
[ colorWithRed: green: blue: alpha:];
}
customColorExample() {
*view = [[ alloc] init];
view.backgroundColor = [ brandPrimaryColor];
}
(, ) *customIdentifier;
- ( *)customIdentifier {
objc_getAssociatedObject(, (customIdentifier));
}
- ()setCustomIdentifier:( *)customIdentifier {
objc_setAssociatedObject(
,
(customIdentifier),
customIdentifier,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
);
}
Categories cannot add instance variables but can add methods and use associated
objects for property-like behavior.
Class Extensions
Class extensions are anonymous categories declared in implementation files that
can add private methods and properties invisible to clients.
@interface Person : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
- (NSString *)description;
@end
@interface Person ()
@property (nonatomic, strong, readwrite) NSString *name;
@property (nonatomic, assign, readwrite) NSInteger age;
@property (nonatomic, strong) NSString *internalID;
@property (nonatomic, strong) NSMutableArray *privateData;
- (void)validateData;
- (void)logAccess;
@end
@implementation Person
- (instancetype)initWithName:( *)name age:()age {
= [ init];
() {
.name = name;
.age = age;
.internalID = [[ UUID] UUIDString];
.privateData = [ array];
[ validateData];
}
;
}
- ( *)description {
[ logAccess];
[ stringWithFormat:, .name, ().age];
}
- ()validateData {
(.name.length > , );
(.age >= , );
}
- ()logAccess {
(, .internalID);
}
- ()fetchDataFromURL:( *)url completion:
( (^)( *data, *error))completion;
(, ) *session;
(, ) *activeRequests;
- ()configureSession;
- ()handleResponse:( *)response data:( *)data
error:( *)error;
- ()init {
= [ init];
() {
.activeRequests = [ dictionary];
[ configureSession];
}
;
}
- ()fetchDataFromURL:( *)url completion:
( (^)( *, *))completion {
*task = [.session dataTaskWithURL:url
completionHandler:^( *data, *response,
*error) {
[ handleResponse:response data:data error:error];
(completion) {
completion(data, error);
}
}];
[task resume];
}
- ()configureSession {
*config = [ defaultSessionConfiguration];
.session = [ sessionWithConfiguration:config];
}
- ()handleResponse:( *)response data:( *)data
error:( *)error {
();
}
- ()loadProfile;
(, ) *nameLabel;
(, ) *profileImageView;
(, ) Person *currentPerson;
- ()updateUI;
- ()showError:( *)error;
- ()viewDidLoad {
[ viewDidLoad];
[ loadProfile];
}
- ()loadProfile {
.currentPerson = [[Person alloc] initWithName: age:];
[ updateUI];
}
- ()updateUI {
.nameLabel.text = .currentPerson.name;
}
- ()showError:( *)error {
*alert = [
alertControllerWithTitle:
message:error.localizedDescription
preferredStyle:];
[alert addAction:[ actionWithTitle:
style: handler:]];
[ presentViewController:alert animated: completion:];
}
Class extensions hide implementation details and provide a clean separation
between public API and private implementation.
Protocol Composition
Protocol composition combines multiple protocols to create precise type
requirements without creating new protocol hierarchies.
@protocol Serializable <NSObject>
- (NSDictionary *)toDictionary;
- (instancetype)initWithDictionary:(NSDictionary *)dict;
@end
@protocol Cacheable <NSObject>
- (NSString *)cacheKey;
- (NSTimeInterval)cacheLifetime;
@end
@protocol Syncable <NSObject>
- (void)syncToServer:(void (^)(BOOL success))completion;
- (BOOL)needsSync;
@end
void saveAndSync(id<Serializable, Cacheable, Syncable> object) {
NSDictionary *dict = [object toDictionary];
NSString *key = [object cacheKey];
NSLog(@"Saving %@ to cache with key %@", dict, key);
if ([object needsSync]) {
[object syncToServer:^(BOOL success) {
NSLog(@"Sync %@", success ? @"succeeded" : @"failed");
}];
}
}
@interface UserData : NSObject <Serializable, Cacheable, Syncable>
@property (, ) *userID;
(, ) *name;
(, ) *email;
(, ) modified;
- ( *)toDictionary {
@{
: .userID ?: ,
: .name ?: ,
: .email ?:
};
}
- ()initWithDictionary:( *)dict {
= [ init];
() {
.userID = dict[];
.name = dict[];
.email = dict[];
.modified = ;
}
;
}
- ( *)cacheKey {
[ stringWithFormat:, .userID];
}
- ()cacheLifetime {
;
}
- ()syncToServer:( (^)())completion {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( * )),
dispatch_get_main_queue(), ^{
.modified = ;
(completion) completion();
});
}
- ()needsSync {
.modified;
}
protocolCompositionExample() {
UserData *user = [[UserData alloc] init];
user.userID = ;
user.name = ;
user.email = ;
user.modified = ;
saveAndSync(user);
}
(, ) <<Serializable, Cacheable>> *items;
- ()addItem:(<Serializable, Cacheable>)item;
- (<Serializable, Cacheable>)itemWithKey:( *)key;
- ()init {
= [ init];
() {
.items = [ array];
}
;
}
- ()addItem:(<Serializable, Cacheable>)item {
[.items addObject:item];
}
- (<Serializable, Cacheable>)itemWithKey:( *)key {
(<Serializable, Cacheable> item .items) {
([[item cacheKey] isEqualToString:key]) {
item;
}
}
;
}
Protocol composition creates precise constraints without the complexity and
fragility of deep protocol inheritance hierarchies.
Best Practices
-
Use protocols for abstraction and polymorphism to define contracts that
enable flexible, testable architectures
-
Make delegates weak properties to prevent retain cycles in delegation
patterns common in Cocoa and UIKit
-
Organize large classes with categories by splitting implementations
across files for related functionality
-
Hide implementation details in class extensions to provide clean public
APIs while keeping internal complexity private
-
Check optional method implementation with respondsToSelector: before
calling to prevent crashes
-
Adopt NSObject protocol in custom protocols to inherit basic object
methods like isEqual: and hash
-
Prefer protocol composition over inheritance to combine requirements
without creating complex hierarchies
-
Avoid adding state in categories as instance variables aren't supported;
use associated objects sparingly
-
Document protocol semantics clearly beyond signatures to explain expected
behavior and usage contracts
-
Use unique category names by prefixing with project or company
identifier to prevent name collisions
Common Pitfalls
-
Adding instance variables in categories is not possible and causes
compilation errors; use associated objects if needed
-
Category method name collisions overwrite existing methods without
warning, causing subtle bugs
-
Not checking optional protocol methods before calling causes crashes when
adopters don't implement them
-
Forgetting to mark protocols as NSObject-conforming loses basic methods
like respondsToSelector:
-
Overusing associated objects for state in categories creates hard-to-find
bugs and memory management issues
-
Creating circular protocol dependencies makes headers difficult to
compile and organize
-
Not declaring protocol conformance in header when implementing in
implementation file hides adoption from clients
-
Using protocols as weak types incorrectly by not understanding that
protocol types don't support weak without explicit storage
-
Creating overly large protocols that mix unrelated concerns violates
interface segregation principle
-
Assuming category load order can cause issues if initialization depends
on specific category loading sequence
When to Use This Skill
Use protocols when designing abstractions, delegation patterns, or data source
interfaces in iOS, macOS, watchOS, or tvOS applications.
Apply categories when extending system classes like NSString or UIColor, or
organizing large class implementations across multiple files.
Employ class extensions to hide private implementation details, IBOutlets, and
internal properties from public headers.
Leverage protocol composition when creating precise type requirements that
combine multiple capabilities without inheritance.
Use optional protocol methods for delegate and data source patterns where
implementers should only provide relevant callbacks.
Resources