|
When I first started learning Cocoa I ran across a design pattern that I had seen implemented a few times before but I had yet to see it labelled with a name. This pattern is called the Delegate design pattern. Coming from C#, I found this initially confusing because in C# the concept of a delegate is slightly different than the concept of a delegate in the Cocoa world. In C#, a delegate is essentially a function pointer, and when someone in Cocoa refers to a delegate, they are referring to an entire instance of a class to which work is delegated.
To see how this pattern works, I needed an example. So, the example I used was a control that is bound to an array of items. So, let's say you have a control that you want to render a list of customers. There are two really obvious approaches to this problem: data binding and the Delegate design pattern.
In data binding, the idea is that your controller will "stuff" the data into the view in some fashion. Often this involves cramming the entire result set into the view and letting the view deal with things like scrolling or paging. You can almost think of the Delegate design pattern as reverse data binding - the control asks the controller for the data it needs, and only the data it needs. The benefits here are that in some data binding scenarios, it becomes extremely easy to tie the view too closely to the controller - making it difficult to use the view for anything but a specific type of data. In addition, if you're not using something like the delegate design pattern, it also becomes easy to get inefficient and cram immense result sets into the control when you really only need to be working with a small window into the result set at any given time.
So, let's take a look at a quick sample. Assume that you're building a custom view in Cocoa called RadialLayoutView. This view lays out all of its subviews so that they are all arranged in a circle. Each of the subviews is actually a piece of data with a name and description. You could easily (especially if you're coming from the WPF world) data bind the control to an array of a custom type, say RadialNode or something. However, there is a way to make it so that your control only ever has to worry about those nodes that are on the screen at a given time, and it doesn't need to know anything about the underlying model that is supplying the name and description values.
The delegate is going to be a pointer to an object that will do the work required of the delegate. In this case, we're going to want to make a delegate that will provide the control with the count of all nodes in the system, and it will also provide the name and description of a single node. Whenever the control needs an item, it can simply ask the delegate for one. This provides a huge amount of power and flexibility and loose coupling between the view and the model that you don't normally find in standard databinding patterns.
To tell the application what the control demands of its delegate, we can use a protocol:
@protocol RadialLayoutDelegate
@required
- (unsigned) countNodes;
- (void) nodeAtIndex: (unsigned)index
name: (NSString **)outName
description: (NSString **)outDescription;
@end
Yes, there's some Leopard syntax here, but it's stuff that you can find elsewhere so there's no NDA problem. With the protocol in mind, we can define (in the .h file) our custom view as:
@interface RadialLayoutView
{
IBOutlet id<RadialLayoutDelegate> delegate;
}
@end
You may notice some similarity between .NET Interfaces and Cocoa protocols. That, of course, is because they both serve the same purpose - they define a set of constraints to which any object implementing said interface (or protocol) must conform. The difference is that you can have pieces of a protocol be optional using the @optional keyword, whereas in .NET if you implement an interface, you must implement all of it or suffer the consequences.
Now, I can write code inside the custom view that just invokes methods on the delegate in order to get the data it needs in order to create and render its list of subviews. For example, to get the name and description of the root node in the data set:
NSString *rootName;
NSString *rootDescription;
[delegate nodeAtIndex:0 name:&rootName description:&rootDescription];
That's all there is to it. The nodeAtIndex method coupled with the countNodes method are all that's needed to give the control the ability to render a near-infinite amount of data because it doesn't have to maintain it all at once, and the delegate can be fetching that data from XML, from memory, or from Core Data - the view doesn't care.
In short, using the delegate design pattern (in Cocoa or in WPF, for that matter) gives me a level of flexibility and agility that I don't normally see with desktop applications. I'm finding that there are a lot of things that I can do elegantly, simply, and quickly using this pattern that I found tedious or cumbersome using traditional data binding.
Nice series of articles. It's great to see a programmer who is open-minded
about other environments. Many of us get comfortable with one or a few
languages and environments and sort of shut down when challenged with
something different. It's fun watching someone else go through the same
process with Cocoa - it's a fascinating toolset once you wrap your head
around it. It's not perfect, and I certainly wish that Apple would add some
of the spit and polish and developer features that Visual Studio has, but
overall, Cocoa is one of the few truly enjoyable ways to build an
application.
Thanks Jeff. I find that by exploring everything around me, instead of
becoming myopic about my current toolset, I can grow as a person and as a
developer. Learning Cocoa has made me a better Vista programmer, as well as
truly appreciate the pleasure I get from writing Cocoa applications.
Often delegate protocols in Cocoa are informal protocols (aka the delegate
can implement an arbitrary subset of the full protocol). This allows the
delegate to influence only the aspects of the delegate contract that it
needs. The delegating object in its various pathways first checks if its
delegate responds to the particular delegate message and if it does calls
the delegate giving the delegate a chance to modify the behavior of the
delegating object. This allows a delegate to modify the behavior of another
object without forcing the use of a subclass, etc.
Kevin, thank you for writing this series of great articles. Knowing Cocoa
has made me a more proficient .Net programmer (my day job) and I am glad
someone is sharing their similar experience. Hopefully, some crossbreeding
will push both frameworks and software forward to new heights.
Shawn - thanks so much for the comment. I love it when the Cocoa experts
drop by and leave little nuggets of enlightenment :)
Related to what Shawn was commenting, I've to add that among us, people
coming from pre-Bindings Cocoa, it was usual the definition of Categories
to give common structures (like NSArray) the capability to be simple data
sources themselves (thus a sort of delegate) for a number of common views
(NSComboBoxes, NSTableViews, ...). Sometimes, I still prefer that approach,
as it is more traceable while debugging than Bindings.
Kevin, it is nice to see your articles on this, thanks for taking the time
to write them.
I couldn't agree more. The more alternative ways you discover to accomplish
the same task, the better you become at that task and similar tasks. My
exploration of Cocoa has made me a better .NET programmer.
A well explained example for numerical array data sources for NSTableView
is NumericalArrayDS, http://turin.nss.udel.edu/programming . And for some
further hints & tips on how to get delegates do what you want check out htt
p://www.dribin.org/dave/blog/archives/2007/01/25/objc_delegate_optimization
_2 . Very interestings reads as well ....