InformIT

Java Perspective: Cocoa Subclasses and Delegates

Date: Nov 4, 2005

Return to the article

Coming from his background in Java development, the concept of using delegates and categories, as opposed to subclassing, was a bit foreign to Marcus Zarra. In Java subclassing, nearly everything was quite common. So common in fact, that Sun provided generic subclasses in quite a few cases. Objective-C and Cocoa, however, have a different approach. Marcus walks you through the different approaches used in Objective-C programming.

In the object oriented programming world, subclassing an object is the meat and potatoes of software development in that it is one of the most fundamental pieces of OOP. Being able to take a class and extend it to add your own flexibility is a powerful thing. This lends itself to code reuse and good coding practices.

Like all things in programming, this feature can be easily abused. If a developer decides during that subclassing to override a method/function in the parent object, he/she is responsible for properly handling all the functionality that the parent function handled. This can be as simple as calling the parent class's version of the method or can involve reimplementing that functionality. In either case, if the developer makes an error in their new implementation of the method, it can cause subtle errors in the application that are difficult to locate. Things that once worked in a consistent manner can change unpredictably.

Objective-C and Cocoa have taken a slightly different approach to the issues surrounding class subclassing. In many of the situations in which subclassing would normally be appropriate, Cocoa has a delegate.

Delegates Can Control Behavior

In the most basic sense, a delegate is akin to a listener in Java. A delegate is one class that is referenced by another. A class that has a delegate (often a GUI class) will call methods on the delegate to determine when and how to do certain things. For example, let's take a look at NSTableView from Cocoa's AppKit framework. In this class, it has several delegate methods—here is a sample of them:

- (void)tableView:(NSTableView *)tv shouldSelectRow:(int)row
- (void)tableView:(NSTableView *)tv shouldSelectTableColumn:(NSTableColumn *)tc
- (BOOL)tableView:(NSTableView *)tv shouldEditTableColumn:(NStableColumn *)tc row:(int)row
- (void)tableViewSelectionDidChange:(NSNotification *)notification

In this sample, you can see that the delegate is responsible for which cells are editable and whether or not a row or column should be selected. In Java, this would be handled via subclassing either the JTable itself or its model. With Cocoa/Objective-C, a simple class is created and it is then set as the TableView's delegate.

As can also be seen by this sample of methods, it is possible to use one delegate to handle many different tables. Because a pointer to the table view is always passed into the method, it is possible to write a delegate to handle several tables or even all of the tables in an application!

Without having to subclass any of the GUI components, the components can be controlled with a small amount of code. This also makes working with Interface Builder much easier because you do not have to constantly set custom subclasses. Instead, you simply instantiate the delegate and Control-drag from the component to the delegate to set it—further reducing the amount of code needed in a Cocoa application.

It is important to note, that unlike Java's listener design, Objective-C objects can only have one delegate. In Java, it is quite possible to have 12 different objects listen to the events from one table, but there can be only one in Cocoa.

Delegates Can Alter Appearance

In addition to being able to control whether an object should be selected or editable, a delegate can be responsible for the appearance or display of an object. In NSBrowser, for instance, there is a delegate method (browser:willDisplayCell:atRow:column:). This method is called just before each cell in the browser is displayed. In this method, a delegate can decide to change how the cell looks when it gets drawn:

- (void)browser:(NSBrowser *)browser willDisplayCell:(id)cell atRow:(int)row column:(int)column {
 [cell setTitle:[NSString stringWithFormat:@"Cell at Row: %i, Col: %i", row, column]];
}

For this browser, each cell will display its row and column when it is drawn. Other attributes can be set during the execution of this method as well, including whether the cell is a leaf, the font used, whether the cell displays an image, and so on. See the documentation on NSCell for more information on what can be configured.

Although Java does not have a counterpart to NSBrowser (except for the version provided by Apple), similar functionality can be built using a combination of JTree and JTable. However, the amount of code required is quite extensive and beyond the scope of this article. In a JTable, it is normal to extend the cell renderer if you need dynamic functionality. With a delegate, this class extension can be avoided.

A large number of the Cocoa components have delegates that serve this same purpose. They give the developer an opportunity to set properties on the component before it is drawn to the screen. Window titles can be altered, sizes and positions can be changed—just about any property can be set by the delegate without a need to extend any of the classes.

Subclassing Still Exists

Even if delegates give the developer a powerful new option for Cocoa development, subclasses still exist. The most basic example of this is NSObject. Because every object extends from NSObject, it is required to at least extend from NSObject.

In the new core data framework, it is very common to subclass NSManagedObject as well. Although NSManagedObject handles all the heavy lifting for core data quite well, there are still certain things best handled by subclassing. For instance, I prefer to have a creation date set in every object I store in my applications. (A very old habit of mine brought on by dealing with a lot of DBAs.) Therefore, my core data objects routinely override the awakeFromInsert method as follows:

- (void)awakeFromInsert {
 [self setValue:[[[NSDate alloc] init] autorelease] forKey:@"createDate"];
}

With this simple method, I am assured that every new object I create in my core data applications will automatically have the create date set for them. This same method can be utilized to confirm that relationships are in place, set transient values, send out notifications, and so on.

Categories, Avoiding Subclassing

Another way to avoid subclassing objects is by the use of Categories, which allow you to add methods to an existing class without subclassing that object. By adding a Category to a class, all instances of that class gain the Category within the scope of your application. You can also override an existing method by declaring it within a Category. To create a Category, it needs to be defined in a header file as follows:

#import "NSObject.h"

@interface NSObject (CategoryExample)

- (int)retainCount;

@end

After the header file is created, the implementation file would be as follows:

#import "CategoryExample.h"

@implementation NSObject (CategoryExample)

- (int)retainCount {
  return 0;
}

@end

In this simple example, I have caused retainCount to return zero for every single class in the entire application! This is because all classes extend from NSObject, and I have altered the behavior of the retainCount: method in NSObject itself. I have not extended NSObject, but have altered the behavior of NSObjet itself.

Categories are very useful in that they allow the developer to alter the behavior of an existing class without subclassing it and without having to repoint everything to the subclass. By simply adding a Category, additional behavior can be included with any class, and the entire application will gain the benefit of the additional methods.

Java does not have anything that resembles this functionality. In Java, there is no way to "inject" code into an existing class without subclassing that class or replacing it. Although this probably makes Java more secure, it certainly adds some very nice benefits to Objective-C development.

Conclusion

In Cocoa/Objective-C there does not appear to be a hard and fast rule to determine whether a developer should subclass or use a delegate. With existing framework classes, the decision has been made for you. If the class has a delegate, check to see whether the delegate can be used for the functionality you are looking for. If not, you may have no choice but to subclass.

Categories are great for adding additional functionality to existing objects or even correcting the behavior of existing objects. However, with that power comes a much larger risk of causing unexpected changes in your application. This is especially true if another class is expecting the incorrect behavior that you correct!

Between Categories and Delegates, I rarely find myself extending any class in Cocoa/Objective-C other than NSManagedObject and NSObject. There rarely is a need. As with all things in programming the old adage of the "right tool for the job" applies. However with Cocoa, there are quite a few new tools to play with.

800 East 96th Street, Indianapolis, Indiana 46240