« Extend with Delegates

Posted by Brad Miller on June 27, 2003 [Feedback (12) & TrackBack (0)]


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


Comments

I assume that the last source example method name should be dealloc:, not setDelegate:...

Posted by: Chris on June 30, 2003 02:00 AM

Yeah, its fixed now. Thanks for the catch. That slipped through somehow. I hate it when my mind interprets something the way it should be instead of how I actually wrote it. =)

Posted by: Brad on June 30, 2003 02:10 AM

it is also a good strategy to test for availability of delegate methods in the setter methods, so:

- (void) setDelegate: (id) newDelegate {
// the usual stuff...

_delegateImplementsOurMethod = [_delegate respondsToSelector: @selector(ourDelegate)];
}

_delegateImplementsOurMethod is a BOOL instance variable. You will query this variable instead of asking the delegate every time you will execute the delegate method. Less Messaging.

Posted by: Armin on June 30, 2003 05:44 AM

What if the delegate is set in IB - as is often the case?

It certainly looks as if the setDelegate: method is automagically called when the nib loads!

Posted by: Bertil on July 3, 2003 03:14 PM

IB uses Key-Value-Coding to set the bindings of it's objects. Key-Value-Coding would certainly be worth an article in itself. However it will call -takeValue:forKey: on the object and this method looks for an accessor -setDelegate: and if that is not present sets the instance variable directly.

So -takeValue:forKey: works wether you have the accessor or not. Quite useful, especially as Key-Value-Coding is much more complex than that.

Posted by: Armin on July 4, 2003 01:46 PM

I'm fairly new to Cocoa, so this question may be obvious. But why do we declare a category of NSObject for our delegate methods? Is it so we can implement them without declaring them in the header file, or so that we don't get compiler warnings when we call the methods on a variable of type id? Or is it some other reason I don't quite see?

What would happen if it were left out?

Posted by: Mark Josef on July 12, 2003 01:41 AM

Hmm, why begin the class variable with _ and the accessor method without it?

I thought in Obj-C they can/should be the same. Is this a convention i"ve not yet come across?

Otherwise, a nice example. Apple's book on Objective0-C does not talk about delegates.

jw

Posted by: Jeff Szuhay on July 14, 2003 12:54 PM

Why use delegates? And why not?

see <http://www.stepwise.com/Articles/Technical/2000-03-03.01.html>

It complements t his article nicely.

Posted by: Jeff Szuhay on July 15, 2003 03:58 PM

Why use delegates? And why not?

see <http://www.stepwise.com/Articles/Technical/2000-03-03.01.html>

It complements t his article nicely.

Posted by: Jeff Szuhay on July 15, 2003 04:00 PM

Regarding Armin's suggestion for optimizing the delegation using BOOLs:

It's not necessary. According to Hillegass' Cocoa book (p. 11), "...the result from -respondsToSelector: is cached by the object with the delegate outlet. This makes performance considerably faster than would be implied by the foregoing code."

Posted by: Mr. Scott on July 18, 2003 11:08 AM

Jeff,

Using an underscore prefix on instance variables and not accessors is a convention that helps the developer keep track of when they're directly manipulating an ivar versus using the accessors.

This works in hand with another convention: that one should _always_ use accessors throughout one's own code. The benefit being that there is one bottleneck where any access to the ivar happens, making it easier to debug problems involving that ivar.

Of course, there may be times when it is more efficient or convenient to access the ivar directly. If you use the underscore prefix on your ivar, you'll be able to more immediately visualize where in your code you are directly manipulating your ivar (versus using the accessor).

It's just a convention. You can name the ivar and the accessor's the same and not lose any karma points. :-) The underscore prefix naming convention just makes the separation between ivar and accessor more obvious.

Posted by: Mr. Scott on July 18, 2003 11:25 AM

I am absolutely new to Cocoa / Obj-C. Thus I have very strange questions, questions only a beginner mights ask. I am writing them here more as a suggestion than to get an answer (because I hope that I will find out the anwer soon by myself):

Note: My fault: I did not work with NSTableView yet.

- Could you explain the mecanism by which a class passes a method call to a delegate. How is this done by the runtime system? This would make the above steps clearer.

- What does "retain" mean? Why not use a hyperlink to an artikle (or a glossary) which explains "retain".

- Could you explain "informal protocol"? Is it like virtual vs pure virtual in C++?

- In someMethodInCDCOurClass you delegate to (the method) ourDelegate of the delegate. How does the "user" of our class know, that he might implement this class into a delegate? Can he inspect our class or is this just some stuff which has to be in a human readable documentation.

Thanks.

Posted by: Christian Fries on August 11, 2003 04:48 AM
Post a comment