// Introduction
We've all used delegates in the Cocoa frameworks. Classes like NSTableView and NSToolbar use them to provide the data they display and to extend their functionality. Today we'll talk about implementing delegates in our own classes. It's a relatively simple thing to do, but there are a couple gotchas that you have to watch out for.
// Interface
The first step in creating a delegate is adding the needed support structure to the class's interface. An instance variable that will hold the reference to the class we will delegate to is added along with the prototypes for its accessor methods. The type of the variable is id since the object can be any class that wishes to conform to the delegate.
#import <Foundation/Foundation.h> @interface CDCOurClass : NSObject { ... id _delegate; ... } ... - (id)delegate; - (void)setDelegate:(id)new_delegate; @end
The other item we have to add to the header file is the interface for the delegate methods. It is an informal protocol that is declared as a category of the NSObject class. NSObject is used because every class inherits from it and can therefore implement the methods.
@interface NSObject (CDCOurClassDelegate) - (void)ourDelegate; - (id)anotherDelegate:(id)p1 withParam:(id)p2; @end
// Implementation
The accessor methods are implemented in a manner similar to any other set of accessors. There is one very important difference. The delegate class is not retained. If it was retained, a circular reference would be created since CDCOurClass is an object in the delegate class. This fact is also the reason that it is safe not to retain. The purpose of retaining is to keep the object from being deallocated while it is still being used. Since the instance of CDCOurClass resides inside the delegate, the CDCOurClass instance will be destroyed before the delegate is deallocated.
- (id)delegate { return _delegate; } - (void)setDelegate:(id)new_delegate { _delegate = new_delegate; } @end
The delegate is all set to go now. To invoke one of the delegate methods, it gets called like any other method. The delegate class may no implement the delegate though. It should be checked first to make sure it responds to the method call. The check can also be cached in the set accessor to improve efficency and performance.
- (id)someMethodInCDCOurClass { ... if ([_delegate respondsToSelector:@selector(ourDelegate)]) [_delegate ourDelegate]; else { [NSException raise:NSInternalInconsistencyException format:@"Delegate doesn't respond to ourDelegate"]; } ... }
// Notify
The standard behavior of a Cocoa class is that the delegate class is automatically registered for any notifications that the delegator posts. This etiquette should be extended if your class posts any notifications. In the "setter" accessor, each notification that the delegate class responds to gets added to the notification center as an observer.
- (void)setDelegate:(id)new_delegate { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; if (_delegate) [nc removeObserver:_delegate name:nil object:self]; _delegate = new_delegate; // repeat the following for each notification if ([_delegate respondsToSelector:@selector(ourNotifName:)]) [nc addObserver:_delegate selector:@selector(ourNotifName:) name:CDCOurClassourNotifNameNotification object:self]; }
The last thing left to do is to unregister the delegate when the class gets deallocated.
- (void)dealloc { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; if (_delegate) [nc removeObserver:_delegate name:nil object:self]; [super dealloc]; }
// Conclusion
That pretty much covers how to add a delegate. The key points to remember are: do not retain the delegate, register the delegate for any notifications, and check that the delegate responds to a method before calling it. Have fun with them.