Sunday, March 29, 2009

Differences in Delegation

Cocoa and Cocoa Touch obviously have a lot in common. They use the same underlying language, they both utilize Foundation classes, and they both follow many of the same design patterns. The fact that they are designed to work for different kinds of physical hardware and the fact that Cocoa Touch was created nearly twenty years after Cocoa (née NexSTEP), means that there are some areas that are very different. These differences can throw you, since so much between the two is the same.

The most obvious of these differences is that the concept of generic view controller classes is "baked in" to Cocoa Touch, but were added to Cocoa after it had been around for years. But that's a topic for a separate blog posting. Today, I want to talk about another, slightly more subtle difference, which is that Cocoa and Cocoa Touch implement delegates in completely different ways. Let's look at the most commonly used delegate objects: the application delegates. The two frameworks' application delegates—UIApplicationDelegate and NSApplicationDelegate— serve the same purpose, but are implemented differently.

Delegation is not unique to Objective-C. It's a recognized pattern that is used in many languages, albeit sparingly in most. Because of Objective-C's dynamic, loosely typed nature, the Apple and NeXT engineers realized early on that delegation was often a better choice than inheritance, which is why the class hierarchy for Cocoa and Cocoa Touch is generally flatter than the hierarchies of object-oriented application frameworks built in other languages. If you have come to Objective-C recently from another OO language, whenever your first impulse is to subclass, take a step back and ask yourself if another design pattern, like delegation or a category, doesn't fit better in light of the language you are using.

Anyway, back to the application delegates. The big difference between UIApplicationDelegate and NSApplicationDelegate is in what they are. NSApplicationDelegate is an informal protocol, which means that it's simply a category on NSObject. Below, you can see what NSApplicationDelegate looks like in Leopard. I have taken out comments and pre-compiler macros and reformatted it to make it easier to read. You can find the original in <Cocoa/NSApplication.h>:

@interface NSObject(NSApplicationDelegate)
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames;
- (BOOL)application:(NSApplication *)sender openTempFile:(NSString *)filename;
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
- (BOOL)application:(id)sender openFileWithoutUI:(NSString *)filename;
- (BOOL)application:(NSApplication *)sender printFile:(NSString *)filename;
- (NSApplicationPrintReply)application:(NSApplication *)application
printFiles:(NSArray *)fileNames
withSettings:(NSDictionary *)printSettings
showPrintPanels:(BOOL)showPrintPanels;

- (void)application:(NSApplication *)sender printFiles:(NSArray *)filenames;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender;
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag;
- (NSMenu *)applicationDockMenu:(NSApplication *)sender;
- (NSError *)application:(NSApplication *)application willPresentError:(NSError *)error;
@end

This may seem rather odd. Why would we declare a category on NSObject for delegate methods? Depending on your language background, you might be wondering why this isn't a protocol or interface. The answer is simple, really. In Objective-C prior to 2.0, protocols (sometimes referred to as "formal protocols") did not allow optional methods. If you conformed to a protocol, you had to implement every method in that protocol. That wouldn't have worked very well; the Apple and NeXT engineers didn't want to force programmers to respond to every conceivable method any application delegate would ever need in their own delegates. Rather, they wanted to let programmers implement only the delegate methods that they needed. By declaring it as a category and creating what we call an "informal protocol", the compiler and the programmer are told what methods this delegate can respond to, but no obligation is imposed on the programmer to implement any particular method. It's perfectly valid (though silly) for a delegate to respond to none of the delegate methods.

In the Cocoa approach to delegates, the mutator method for a delegate usually looks like this:

- (void)setDelegate:(id)anObject;

In other words, an instance of any class can be set as the delegate. If that delegate implements a particular delegate method, that method will be called at the appropriate time. If it doesn't implement it, NSApplication will simply skip the call and continue execution of the program. It's a very laissez-faire approach that puts a lot of trust in the programmer. That's Cocoa in a nutshell, really. Objective-C doesn't give the programmer mechanisms to completely lock out other programmers the way, say, Java, C++, and C# do. There are no final classes, and declaring things @private is really more of a suggestion. The nature of the language leads to a different approach to many things. It may seem weird - even wrong - if you come from one of the many languages that derive their object-model from the far-less-trusting Simula, but give it time. Properly used, it's very elegant.

On the other hand. UIApplicationDelegate, the iPhone's application delegate, is not implemented using a category, it's implemented using a formal protocol. Since the iPhone was developed after Objective-C 2.0 was released, Apple had the option of using optional methods in formal protocols, so in Cocoa Touch most (I think all) delegates are defined as formal protocols. This is what the application delegate looks like in Cocoa Touch. Again, I have reformatted and removed comments to make it read easier in this context:

@protocol UIApplicationDelegate<NSObject>
@optional
- (void)applicationDidFinishLaunching:(UIApplication *)application;
- (void)applicationDidBecomeActive:(UIApplication *)application;
- (void)applicationWillResignActive:(UIApplication *)application;
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application;
- (void)applicationWillTerminate:(UIApplication *)application;
- (void)applicationSignificantTimeChange:(UIApplication *)application;
- (void)application:(UIApplication *)application
willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation
duration:(NSTimeInterval)duration;

- (void)application:(UIApplication *)application
didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation;

- (void)application:(UIApplication *)application
willChangeStatusBarFrame:(CGRect)newStatusBarFrame;

- (void)application:(UIApplication *)application
didChangeStatusBarFrame:(CGRect)oldStatusBarFrame;

@end


It's really not all that different. Of course, the delegate methods are not exactly the same due to the different nature of the devices for which Cocoa and Cocoa Touch were designed, but this protocol says basically the same that our earlier informal protocol said: that any object can be a delegate, and since it declares all of the methods as @optional, the delegate only needs to implement those methods it cares about. The mutator method for a delegate in Cocoa Touch looks similar to the one in Cocoa, but with two differences. First, UIApplication uses Objective-C 2.0's properties and synthesizes the mutator rather than declaring a mutator manually. Second, and more important, though it still accepts id (meaning any object), it requests that the object being assigned as the delegate conform to the UIApplicationDelegate protocol:

@property(nonatomic,assign) id<UIApplicationDelegate> delegate;

This being Objective-C, you actually can assign any object to be the delegate, even one that doesn't conform to the protocol, but if the assigned object's class doesn't explicitly conform to UIApplicationDelegate, you will get a compile-time warning. Fortunately, all the iPhone application templates give you your application delegate, and the provided delegate's class already conforms to that property, so you rarely ever need to do that step for the application delegate, but when it comes to the countless other delegates in Cocoa Touch, you will need to explicitly conform your class to the delegate protocol.

Whether Cocoa will switch to using formal protocols is, as of now, an unanswered question. So far, I've seen no indication of Apple making this change. As of Leopard, every delegate I can think of is implemented as a category on NSObject, but I also don't have any more information about what's going in inside the loop than most of you do and don't know if they have plans to change this. They might. They might not.

What I do know is that not everybody agrees that the newer way of doing delegates is better. I've talked to a number of old-time Cocoa/Mac programmers who view the new approach as less elegant and see it as an unnecessary change. I don't really have much of an opinion, to be honest. As a practical matter, there's not much of a difference in the way we use delegates with either approach. Other than conforming our classes to one protocol, pretty much everything works the same.

The new approach is a tradeoff. It adds some compiler-time checks that aren't available with informal protocols, but it also has some unintended consequences. For example, when you have subclasses of classes with delegates, you can have situations where a delegate has to conform to a protocol for an object for which it's not the delegate. You can see an example of this in Beginning iPhone Development in Chapter 16. On page 467, we conform CameraViewController to UINavigationControllerDelegate, even though we're not a delegate of a UINavigationController. Why? Because UIImagePickerController is a subclass of UINavigationController. Because both of those classes have delegates, and their delegates require different protocols. As a result, the compiler forces us to conform to both protocols. It's a pretty minor inconvenience - it requires typing a single class name - but this seems to rub some Cocoa developers I've talked to as being very "un-Cocoa-like" and inelegant.

Let's finish off with one important last bit of information about delegates that applies to both Cocoa and Cocoa Touch: as a general rule, objects do not send a -retain message to their delegate. There are a few exceptions to this, but unless a class is specifically documented as retaining its delegate, assume that they don't. If your delegate gets deallocated before the class it is a delegate for, you should make sure to set the class' delegate to nil before your class is deallocated to avoid problems.

No comments:

Post a Comment