Mastodon

Effectively implementing delegate pattern in your custom classes

Delegate is one of the simplest and flexible patterns in Cocoa.

A delegate is an object that acts on behalf of, or in coordination with, another object when that object encounters an event in a program.

Concepts in Objective-C Programming

By using delegate, you simplify the design and reduce the coupling between individual components.

If you have developed any iOS or Mac apps, you probably already learned and used delegates. Let's talk about how to implement delegates in your own classes.

Declare protocol for the delegate

First, you must declare a protocol.

@protocol MyControllerDelegate
-(void) myController:(MyController*)myController didSubmitRequest:(MyRequest*)request;
@optional
-(void) myController:(MyController*)myController willOpenRequest:(MyRequest*)request;
@end

Note any method in @optional sections is ... optional: i.e. class can choose not to implement those methods.

Define a property for the delegate in the custom class

You custom class must hold a reference to the delegate.

@property (nonatomic, weak) id<MyControllerDelegate> delegate;

Note the delegate is declared as weak to avoid retain cycles.

Invoke delegate methods

In the custom classes, invoke the delegate methods when appropriate. Note if the protocol is optional, you should check the the delegate actually implemented the method by respondsToSelector: method.

- (void)openRequest {
    if ([_delegate respondsToSelector:@selector(myController:willOpenRequest:)]) {
        [_delegate myController:self willOpenRequest:self.request];
    }
    
    // some actual work
}

Tip: Use struct to make optional delegate readable

If your delegates is getting large, you might found checking delegates with respondsToSelector: at each method invocations are too verbose.

To DRY this up, you can define a struct in the custom class:

typedef struct
{
    unsigned int willOpenRequest;
    unsigned int didOpenRequest;
    unsigned int willCloseRequest;
    unsigned int didCloseRequest;
} MyControllerDelegateCache;

Now add a property delegateRespondsTo in your custom class.

@interface MyController ()
@property (nonatomic, assign) MyControllerDelegateCache delegateRespondsTo;
@end

Instead of using automatic synthesized setter, implement your own like this:

- (void)setDelegate:(id<MyControllerDelegate>)aDelegate
{
    if (_delegate != aDelegate) {
        _delegate = aDelegate;
        _delegateRespondsTo.willOpenRequest = [_delegate respondsToSelector:@selector(myController:willOpenRequest:)];
        _delegateRespondsTo.didOpenRequest = [_delegate respondsToSelector:@selector(myController:didOpenRequest:)];
        _delegateRespondsTo.willCloseRequest = [_delegate respondsToSelector:@selector(myController:willCloseRequest:)];
        _delegateRespondsTo.didCloseRequest = [_delegate respondsToSelector:@selector(myController:didCloseRequest:)];
    }
}

Now instead of ...

- (void)openRequest {
    if ([_delegate respondsToSelector:@selector(myController:willOpenRequest:)]) {
        [_delegate myController:self willOpenRequest:self.request];
    }
}

You can write:

- (void)openRequest {
    if (_delegateRespondsTo.willOpenRequest) {
		[_delegate myController:self willOpenRequest:self.request]
    }
}

More readable, less typing, huge win.