Objective-C
02 / 02

Protocols, Categories & Runtime Dynamics

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
// ...
@end

Runtime: 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