Re: fundamental question: how do I call controller methods from other classes?
Re: fundamental question: how do I call controller methods from other classes?
- Subject: Re: fundamental question: how do I call controller methods from other classes?
- From: Graham Cox <email@hidden>
- Date: Thu, 22 May 2008 16:57:39 +1000
On 22 May 2008, at 3:53 pm, Johnny Lundy wrote:
Oh, I have no problem invoking API methods that are built-in to Cocoa.
It's dealing with two of my own classes that I never understood.
OK, well alright, here's the secret: there is no difference!
I was referring to something like the following trivial setup:
Two custom classes in my project, MyClassA and MyClassB, each with
its .h and .m files.
Assume MyClassB has an instance method "- (id) solve:aFoo".
I want to send the "solve:" message to an instance of MyClassB from
MyClassA.
Now IF MyClassB were instantiated, and IF its instance were
myClassBInstance, I could write
id myBar = [myClassBInstance solve:myFoo];
From one of my instance methods in MyClassA's instance. (Which would
have been somehow instantiated).
The problem: how to instantiate MyClassB so that it actually HAS a
"solve:" method to send a message to.
I did this once in a project just to see if it would work: from
inside an instance method of MyClassA:
MyClassB *myClassBInstance = [[MyClassB alloc] init];
id myBar = [myClassBInstance solve:myFoo];
And it worked, but it seemed like such a kludge that I just
incorporated both classes together.
That's exactly right. Not sure why you think it's a kludge - possibly
because in a trivial example case like this it seems pointless to have
two separate objects when one will do perfectly. That doesn't mean
your example is wrong, it just means the example doesn't give much
insight as to *why* you'd do this. The example illustrates only the
*how* and does so perfectly!
The advantage comes when your designs get more complex. Objects give
you a clean separation of responsibilities. Why doesn't NSString deal
with bezier paths? After all, it *could*, but it would be a crazy
design, since sometimes you want one and not the other, and they don't
seem related.
Deciding where to break responsibilities down into separate classes is
an art, I have not seen too many text books going into that too much.
The M-V-C paradigm is a shorthand guide to one useful approach to
where certain responsibilities lie, but believe me, there are many
fuzzy edges in between.
Besides, if you don't have IB to instantiate a class, how can you do
it? If there is no instance of the class, where would you invoke the
alloc/init messages from? From main()?
Well, it depends. But never main() - I never touch main in a Cocoa
project.
I usually instantiate objects on a "need to use" basis. So if my class
always needs certain objects, I might instantiate them in my init
method (and thus get rid of them in my dealloc method). Or at any
other time that I need something that another object can do for me.
Example, I have a custom view class. In my -drawRect method I'd like
to draw a solid red box somewhat inside my bounds.
- (void) drawRect:(NSRect) updateRect
{
// I want to draw a box inside my bounds. So get the bounds and inset
it:
NSRect myRect = NSInsetRect([self bounds], 10, 10 );
// I'd like it drawn in red. I can use the NSColor class to set that
up:
NSColor* red = [NSColor redColor]; // get an instance of NSColor I'm
calling "red"...
[red set]; // ...and make it the current drawing colour
// Hmm, I can't be bothered to work out how to fill the rect pixel by
pixel in video memory, but I know another object that can do it
// for me, so let's just make one and give him the job:
NSBezierPath* myPath = [NSBezierPath bezierPathWithRect:myRect]; //
get a new bezier path instance based on my rect...
[myPath fill]; // ...and make it fill itself
// OK, job done - and I barely got my hands dirty ;-)
}
Note that in this case the objects are instantiated by their class
convenience methods, and autoreleased. But it would be equally valid
to use alloc + init and then release them directly when you're done
with them. Here we are using Cocoa's own objects, but if you had a
class you made called 'MyColouredRectFiller' for example, you could
make one of those, hand the job to it then throw it away afterwards.
Why bother, when you can just write the code right here? Well, suppose
you have another completely unrelated place where you want to draw a
coloured rect. You can use a MyColouredRectFiller there too which
you've already written and debugged for this case. So just use it.
That's the real benefit of objects - reusing code that you already
wrote and debugged once, so you can save time by not having to do it
again. When objects get more complex this time saving is very, very
significant. And when whole projects get more complex, it's about the
only way that makes it even remotely manageable.
You might argue that this is just the same as having a 'fill rect'
procedure that you can call from two places, and so it is. But once
again, it's the simplicity of the example that is misleading. Instead
of an object to fill a rect (there isn't one, so that tells you it's
probably too simple to be worth it) imagine you want to edit some
text, word-processor stylie. Your client code just wants to get some
text from the user, it doesn't care how, but you don't want your app
to be hideously unusable either. So you can use an NSTextView object
to handle that (very complicated) job in the standard way. You
couldn't really do that with a simple procedure call (old timers will
not remember Apple's procedural text editor, TextEdit, with much
fondness I imagine). Objects are useful for encapsulating a unit of
functionality in a way that can be easily reused in many similar
situations, but which have no other dependencies (if well written).
And if you DO have IB, then is the usual technique to instantiate
one class in IB, and then code the instantiations of other classes
in the first one, as I did above? Or create one IB generic object
for each class and give it an IBOutlet so it has a name?
My view is that Interface Builder should *only* be used to set up
interfaces, and no more. It can actually go further and set up a lot
more, but I think it's a mistake to get too carried away with
instantiating objects in IB just because you can. Apart from anything
else it doesn't help you learn how to do it manually, which for a
project of any substance you *must* know. I typically go as far as my
first level controller in IB (so that I've got *something* that has
all of those IBOutlets to wire up to controls) but everything else is
done in code. So IB instantiates the controller, but all of its actual
"guts" you write yourself (I'm deliberately leaving bindings out of
this).
On the other hand you could create everything in code - interface
included, but that's very tedious as well - after all, the positioning
and sizing of interface items is more to do with aesthetics than for
any technical reason, so IB is great for allowing you to do things "by
eye" that would be a real chore in code. The point is that given a
project, you have tools optimised for some parts of the job but not
all of it, but there's some overlap. You need to learn when to use one
and not the other.
This is not in the docs, by the way. I think they just assume that
you know it.
I'm not sure that's true. The docs do tell you what IB is good for,
but they do not tell you in great detail how it may also be abused in
all the ways it could be. I think you'll find that's true of almost
any product or object in the real world that needs any explanation.
(Though that's changing - e.g. "petrol fumes may be harmful if
inhaled" well, whodathunkit?)
I've always avoided it by using just one class. I never understood
the need for more than one class anyway.
For a very small project or simply learning the ropes that might be
all you need, but most projects of any size will have many classes -
possibly thousands. Clearly, the existence of all these separate
classes should tell you that people do think they are a good idea! You
might not "get it" yet, but when you do, it will be a real "of
course!!!" moment. Let's hope it's soon ;-)
cheers, G.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden