Objective-C: Protocols, Categories & Runtime Dynamics
Protocols & Delegation
// Protocol declaration — an interface contract
@protocol PersonDelegate <NSObject>
@required
- (void)person:(Person *)person didUpdateAge:(NSInteger)newAge;
@optional
- (void)personDidLogout:(Person *)person;
@end
// Adoption
@interface PersonViewController : UIViewController <PersonDelegate, UITableViewDataSource>
@end
@implementation PersonViewController
- (void)person:(Person *)person didUpdateAge:(NSInteger)newAge {
NSLog(@"%@ is now %ld", person.name, (long)newAge);
}
// Guard optional methods before calling
- (void)notifyLogout:(id<PersonDelegate>)delegate person:(Person *)person {
if ([delegate respondsToSelector:@selector(personDidLogout:)]) {
[delegate personDidLogout:person];
}
}
@end
// Runtime conformance check
if ([someObject conformsToProtocol:@protocol(PersonDelegate)]) {
// safe to treat as id<PersonDelegate>
}Categories & Class Extensions
// Category — add methods to an existing class, even one you don't
// own the source for. Cannot add new instance variables.
// NSString+Trimming.h
@interface NSString (Trimming)
- (NSString *)trimmedString;
@end
// NSString+Trimming.m
@implementation NSString (Trimming)
- (NSString *)trimmedString {
return [self stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
@end
NSString *clean = [@" hi " trimmedString]; // "hi"
// Class extension — an *anonymous* category, usually in the .m file.
// Unlike a regular category, it CAN add ivars/properties, and it must
// be implemented by the class's own @implementation.
// Person.m
@interface Person ()
@property (nonatomic, strong, readwrite) NSDate *createdAt; // public readonly, private readwrite
@property (nonatomic, strong) NSCache *imageCache; // private-only property
@end
@implementation Person
// ...
@endRuntime: Selectors, Swizzling & KVO
// Selectors — a compiled reference to a method name
SEL sel = @selector(greeting);
if ([p respondsToSelector:sel]) {
NSString *result = [p performSelector:sel];
}
// Method swizzling — swap two IMPs at runtime, usually in +load.
// Common use: injecting analytics into an existing method without
// modifying its source. Powerful, but a global side effect — use sparingly.
#import <objc/runtime.h>
@implementation UIViewController (Analytics)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method original = class_getInstanceMethod(self, @selector(viewDidAppear:));
Method swizzled = class_getInstanceMethod(self, @selector(swizzled_viewDidAppear:));
method_exchangeImplementations(original, swizzled);
});
}
- (void)swizzled_viewDidAppear:(BOOL)animated {
[self swizzled_viewDidAppear:animated]; // actually calls the ORIGINAL now
NSLog(@"Screen viewed: %@", NSStringFromClass([self class]));
}
@end
// KVO — the runtime dynamically subclasses the observed object so its
// setter calls willChangeValueForKey:/didChangeValueForKey: around the
// original implementation.
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
NSLog(@"age changed to %@", change[NSKeyValueChangeNewKey]);
}Interop with Swift
`NS_ASSUME_NONNULL_BEGIN`/`END` wraps a header so Swift sees clean `Type` instead of `Type!` implicitly-unwrapped optionals everywhere — only exceptions need `nullable`.
`instancetype` (not `id`) as an initializer/factory return type lets Swift infer the correct concrete type through subclassing.
A Swift class/method needs `@objc` to be visible to Objective-C code, `@selector`-based APIs, or KVO — Swift structs, enums without raw values, and generics generally can't bridge at all.
An Objective-C bridging header (`ProjectName-Bridging-Header.h`) exposes Objective-C classes to Swift files in the same target.
`NSError **` out-parameters bridge to Swift's `throws` automatically when the method follows the `error:` naming convention and returns `BOOL`/nullable.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free