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.
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.