an interesting delegate design issue raised by IB...
an interesting delegate design issue raised by IB...
- Subject: an interesting delegate design issue raised by IB...
- From: "Michael B. Johnson" <email@hidden>
- Date: Tue, 11 Sep 2001 21:56:24 -0700
so there are lots of places in the AppKit that one sees a delegate design
pattern.
As a brief refresher, a delegate pattern is one where to use an instance
of a class A you don't subclass class A, but rather derive a class B (or
add to an existing class B) that conforms to a protocol and then you set
an instance of class B to be the "delegate" of the class A.
Some obvious (and not so obvious ones) Appkit examples would be
NSOutlineView, NSTableView, NSApplication, NSWindow, NSBrowser,
NSColorPanel, NSComboBox, NSCustomImageRep, NSDrawer, NSFontPanel, NSImage,
NSInputServer, NSLayoutManager, NSMatrix, NSPageLayout, NSRulerView, etc.
My experience using delegation in the AppKit usually revolves around two
cases, which I think of as long lived vs one shot (i.e. one where the
delegate is set separately, and then used, vs asking the object to do one
thing and giving it a delegate to use as an argument).
Both of these are from the perspective of the client developer
implementing the delegate, rarely as one designing the class that will use
delegation to accomplish its task.
so...
I've come up with a pretty generic class for viewing images (WK2DImageView)
. It's pretty full featured, letting you have multiple images (that you
either composite together, or tile, or page through one at a time) with a
background color, where you can zoom the image in or out, drag images in
and out, change the gamma, make it fullscreen, etc.
One thing that comes to mind pretty quickly is that it makes sense for
WK2DImageView to use a delegate to supply it with its images (i.e.
something that conforms to the WK2DImagesHandler protocol). This is handy
for several reasons:
- it separates out the management of the images that the view is *not*
displaying to someone else
- it gets code out of the image viewing class (WK2DImageView), which
theoretically shouldn't need to be subclassed most of the time (if I do my
job right)
- it allows a large set of the same images to be shared among a set of
views.
Because I'm me, I wouldn't dream of making this class unless I put it on
an IB palette, which suddenly raises some interesting issues.
One thing that bugs me about most classes that need delegates is that I
still have to write them, even if I'm using the class exactly what it was
designed for. To deal with this, it seems prudent to supply a simple
companion class (WK2DImagesSource) that can easily act as a delegate for
any number of image views to supply them with images.
WK2DImagesSource, of course, is also on the same IB palette and the user
can drag one out and then wire up any number of image views to it.
WK2DImagesSources each have an IB inspector, and the user can drag images
(or use the "add image..." button) to add images to them, and then at run
time these will be viewable from any WK2DImageView that is using it as a
delegate.
Also, we make WK2DImageView conform to the WK2DImagesSources protocol as
well, so that other objects can, very naturally, add images to the image
view directly, although what the WK2DImageView will actually do is
immediately pass the message on to its delegate.
so, here's the conundrum (if anyone is still left with me):
The natural way you would want to add images to an instance of
WK2DImageView in IB is to just drag an image on top of it, right? Most
natural thing in the world...
except that it's using a delegate. Which isn't connected until you go
into test interface mode, archive these objects out, init them from a
coder and have their connections wired up. Which means that anything you
drop on it is going to get dropped into the bit bucket, because at the
point you drop it, that WK2DImageView has no delegate (since you've used
IB to connect it, but that happens to some future instance, not this one).
So here's my idea:
a WK2DImageView has two instance variables, both of type WK2DImagesSource*
. One is also an IBOutlet (let's say it's called _imagesSource) so that a
user sees it in IB, and the other is not (let's call it _myImagesSource).
When the WK2DImageView gets init'ed, it makes/unarchives its own delegate
(a WK2DImagesSource). Then when images gets dragged over it, it hands
these off to its delegate, which happily stores them. Then when we
archive this object, we archive this delegate (_myImagesSource), with its
data. When we finally do our awakeFromNib, if _imagesSource is set, that
means we've been wired up to use some other delegate. We politely have
our _myImageSource delegate hand over its images to this third party
delegate, and then release and set to nil our _myImageSource. If, on the
other hand, the user has neglected to wire this instance to another
_imagesSource, we're already fine, since all of our hand off routines look
like this, where first priority is given to our "private delegate" (for
want of a better term):
- (void)addImage:(NSImage*)image named:(NSString*)name
{
if (_myImagesSource) {
[_myImagesSource addImage:image named:name];
} else {
[_imagesSource addImage:image named:name];
}
return ;
}
So is this completely heinous? Is there an obvious better way to have my
cake and eat it to without resorting to this dual instance variable
chicanery? If so, I'd love to know it.
I'm actually kind of pleased with most of this solution, but I'm sure many
of you (both people who've kept up :-)) are horrified... The big
advantage I see is that it "just works" from the user (client developer)
perspective - they can drop images on the WK2DImageView, they can wire it
up to a separate/shared delegate, which has its own images, and everything
"just works".
Comments?
--> Michael B. Johnson, Ph.D. -- email@hidden
--> Studio Tools, Pixar Animation Studios
-->
http://xenia.media.mit.edu/~wave