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.



7 comments:

hollowout said...

Jeff, what a great article!

You reminded me things Apple said in Objective-C 2.0 guideline before, that using informal protocol could be useful when all the methods are optional, but it's typically better to use a formal protocol with optional methods (so you can have more language support likes type checking at compile time or runtime checks on object conformation with protocol).

Dave said...

Thanks Jeff great article. It is really reminding me that I need to take some time to learn the history and details of Cocoa.

bcc said...

Hello,
allow me to disagree with your example of why formal protocol are sometimes awkward;
we conform CameraViewController to "UINavigationControllerDelegate, even though we're not a delegate of a UINavigationController. Why? Because UIImagePickerController is a subclass of UINavigationController. "
This is actually contracting itself. You are a delegate of a UINavigationController, the second sentences says it, derivation means that you are an element of the superclass, just a specialized one. Therefore this is completely natural. If some of the methods of the UINavigationControllerDelegate are awkward or cannot be applied in this situation it seems that it is more because the derivation between UINavigationController and UIImagePickerController was a bad choice (I haven't looked at those method so I can't say if this is really the case).

I think there may be reason when informal protocol are more appropriate than formal ones (although I don't know any) but I don't feel like you have illustrated one of them.

suryanarayanan said...

Hi Jeff,
i am novice programmer in objective C . Can u explain in detail why Delegate is required and when is the need to implement arises.
Thanks in advance

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

JeansPilot said...

JeansPilot offers the chance to buy a large variety of men’s and women’s jeans clothing from the world famous Italian Brands.
Online jeans clothing store looks for original fashion clothing sales and clearances of worldwide known designers. We participate in fashion auctions to get the lowest possible price for Top quality Clothes, Shoes and Accessories.
Buy Jeans

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels