In article
<be0449a4-03f6-4ba8-a8e1-a3022cce8eb9@[EMAIL PROTECTED]
>,
Cory <cory.imdieke@[EMAIL PROTECTED]
> wrote:
> Hello, I'm a new Mac App Programmer used to Java and a little bit of C+
> +, and I'm trying to wrap my mind around Delegates. How do they work,
> what are they for, etc? Could anyone give me a basic run-down of them
> and how to use them? Is an AppDelegate the same basic concept of an
> AppController, with a bunch of methods that can be called from any Nib
> in the application?
>
> The other basic concept that I'm not too firm on is the File's Owner
> in a nib file. What exactly does it point to, and what defines what it
> is? If I open another window from a method in my AppController with a
> button in my Main nib file, is the new window's File's Owner my
> AppController, or the window that called the method in the
> AppController? What's the Owner of the Main nib?
>
> I have a alright idea of Outlets and so on, basically a way to access
> an object instance in a nib file from code, is that the basic idea?
>
> Thanks for answering my pretty basic questions!
Table of Contents
1.) Introduction
2.) File's Owner
3.) Object's delegate
4.) The Responder chain
5.) Notifications
6.) Key-Value Observing
7.) Conclusion
1.) Introduction
There a bunch of inter-related concepts in this part of Mac programming.
I'll try to explain, but pardon me in advance if I don't do a good job.
2.) File's Owner
A nib file is just an data-structure representation of a network of
objects. Under the hood of NSApplicationMain, your principal class
(specified in your Info.plist) loads your principal nib file. (also
specified in your Info.plist) the sequence of calls eventually calls:
-[NSNib instantiateNibWithExternalNameTable:] to do the actual work of
alloc'ing and init'ing the objects described in the file.
You pass in an object, and in the nib file you've set the class of
"File's Owner", a fictitious instance. NSNib uses the description of the
class to actually assign to the fields of the object you pass in.
For the main nib of the program, the "File's Owner" is the
NSApplication. For secondary nib files, the "File's Owner" is a
NSWindowController. NSWindowController's main job is to turn secondary
nib files into actual windows on demand.
3.) Object's delegate
You rarely subclass NSApplication. You rarely subclass
NSWindowController. Instead, you set their delegates to reference on of
your objects. Both NSApplication and NSWindowController have a list of
methods that they'll call via:
if ([[self delegate] respondsToSelector:@[EMAIL PROTECTED]
(someMethod)]) {
[[self delegate] someMethod];
} else {
[self someMethodDefault];
}
that is, at run time, they'll check if the delegate implements a method,
and if so, they'll call it, otherwise they'll do some default action.
The set of all methods of a delegate that are called this way is called
an "informal protocol", by analogy with an Objective-C "protocol", which
is a set that is checked at compile time, and all the members of the
protocol MUST be present or you'll get a compile error. (Although
Object-C version 2 for OS X Leopard does have additional sup****t for
writing down informal protocols.)
Note that the delegate of an App is often created because there is an
instance in the nib file, but nothing REQUIRES delegates to be in nib
files.
Delegation interacts with reference counting: a NSWindowController
"has-a" NSWindow (i.e., there is a setter/getter pair that "owns" the
window.) if objects retained their delegates, there'd be a loop so their
reference counts would never go to zero, and they'd never be dealloced.
So the convention is that objects don't [ retain]; their delegates.
4.) The Responder chain
Suppose a menu item in a nib wants to be bound to an IBAction of an
object in some other nib. This is one of the uses of the "responder
chain". Under the hood,
[NSApplication sendAction:@[EMAIL PROTECTED]
(action:) to:nil from:aMenuItem]
gets called. Since NSApplication inherits from NSResponder, the code in
NSResponder starts with the current responder, usually a view of a
window, walking up the responder tree to the root, calling
respondsToSelector: until it finds an object that responds to that
message then it calls it. (I'm skipping some details here.)
5.) Notifications
Suppose you need something like delegation, but you expect more than one
object to be interested. NSNotificationCenter lets you register that an
object is interest in a notification, and objects call
[NSNotificationCenter postNotification:...] to trigger all the observers.
For example at app shutdown, you might have multiple listeners for
NSApplicationWillTerminateNotification
Notification interacts with reference counting: if you registered for
any notifications, you must explicitly unregister in your [dealloc],
otherwise NSNotificationCenter will send notifications through a bad
pointer. (If NSNotificationCenter retained all the observers, then none
of them would ever get their reference count decremented to zero, so
none would ever be dealloced)
I don't like Notifications because you can't step through them in the
debugger. Instead, you search over the whole project for calls to
addObserver:... with the correct name, search again for the methods
referenced in those calls, set breakpoint on those methods, then
continue. Then disable all of those breakpoints. What a pain!
6.) Key-Value Observing
Suppose it is a pain to go through your code adding
-[postNotificationName:object:userInfo:] calls in many places. You can
use Key-Value Observing to tell the system: whenever this setter is
called in this object. Call my -[observeValueForKeyPath:(NSString
*)keyPath ofObject:(id)object change:(NSDictionary *)change
context:(void *)context]
This is the ultimate "come from" statement. You are stepping along in
the debugger, executing an innocuous setter, and suddenly some code on
the other side of the program runs, without the debugger telling you
about it.
There are fewer retain/release issues here: observers can't retain
observees for the reasons I've previously described, but since dead
objects don't change, your observeValueForKeyPath: won't get called on a
dead object. You may crash, however, when the observer, in its dealloc,
calls removeObserver:forKeyPath: (someone who knows more about this than
me can let me know if it is mandatory to call
removeObserver:forKeyPath:, or if the system will take care if it for
you.)
Make sure you know about triggerChangeNotificationsForDependentKey: it
is useful in real programs to make sure that all the observers of all
the derived attributes actually get called.
7.) Conclusion
If one object must call methods in another, from tightest binding to
loosest binding:
a. call a method in a member (i.e., there is a setter/getter pair that
"owns" the other object.)
b. call a protocol method in a member (like above, but we don't care
about the specific type, just that implements the protocol.)
c. call a method in a delegate, if it implements it.
d. post a notification (All the listeners will see it, in undefined
order.)
e. As setters go about their business modifying the model,
observeValueForKeyPath:... "magically" fire.
You as the programmer are expected to balance the issues: convenience
versus explicitness. As you go down this list you gain flexibility and
power, but make you program more mysterious and harder for a novice to
figure out.
I hope this puts things in perspective.


|