I have a rather beginner's basic type of question on designated
initializers. The book that I'm using uses an extremely simple (and
trivial) example (which is usually the case) which leaves me with about
as many questions as answers.
Here's the simple example from the book using a Rectangle class which is
a child class of NSObject.
Here's some of the pertinent info from Rectangle.h; I won't bother
including stuff that's not directly related to the example:
Rectangle.h
@property int width, height;
-(void) setWidth: (int) w andHeight: (int) h;
-(instancetype) initWithWidth: (int) w andHeight: (int) h;
-(instancetype) init;
Ok, here are just the new, custom init methods from Rectangle.m
Rectangle.m
//Designated initializer
-(instancetype) initWithWidth: (int) w andHeight: (int) h;
{
self = [super init];
if (self)
[self setWidth:w andHeight:h];
return self;
}
//The override of super's designated initializer
-(instancetype) init
{
return [self initWithWidth:0 andHeight:0];
}
Ok, I get what's going on here: You create a new designated initializer which handles a lot of the ivars that you feel are important. You have
to include a call super's designated intializer and assign it to self,
then the 'if' test, then your initialization code. You then override
super's designated initializer (in this case it's NSObject's init) and
return a call to your designated initializer. I get how the hierarchy
is supposed to work.
But how is this helpful? In my mind it's not helpful for a few
different reasons. Reason 1: In the override to init you've hardwired
the width and height to be 0. That's what NSObject's init would do, so
it's not doing anything new or better. Reason 2: Even if you put
different values in there say, 1 and 2 or whatever values you thought
you might need a newly created Rectangle object to have, they are still hardwired values, buried inside the newly created init method. How is
this helpful at all? You may want to have a bunch of rectangles of all different dimensions. At that point you'd have to change their
dimensions with the proper accessor method. If so, why even both with
this newly created init when you can just use NSObject's init and use
the accessor method to tweak the height and width you want each
Rectangle object to be?
Am I correct that the trivial nature of this example sort of gets in the
way of truly understanding the benefit or need of all of this?
thanks,
jonR
Am I correct that the trivial nature of this example sort of gets in
the way of truly understanding the benefit or need of all of this?
Ok, here are just the new, custom init methods from Rectangle.m
Rectangle.m
//Designated initializer
-(instancetype) initWithWidth: (int) w andHeight: (int) h;
{
self = [super init];
if (self)
[self setWidth:w andHeight:h];
return self;
}
//The override of super's designated initializer
-(instancetype) init
{
return [self initWithWidth:0 andHeight:0];
}
Ok, I get what's going on here: You create a new designated initializer which handles a lot of the ivars that you feel are important. You have
to include a call super's designated intializer and assign it to self,
then the 'if' test, then your initialization code. You then override super's designated initializer (in this case it's NSObject's init) and return a call to your designated initializer. I get how the hierarchy
is supposed to work.
Thanks for the explanations and to be honest I don't fully understand
all of what you have said, but I understand it enough for it to make
sense. However, I realize I need to understand class invariants which
is a new concept to me. I think that is the primary component or
background for me that is lacking and when I get a better
understanding I'll fully understand your explanation.
Jon Rossen <jonr17@comcast.net> writes:
Am I correct that the trivial nature of this example sort of gets in
the way of truly understanding the benefit or need of all of this?
An initializer method may do a little more than just setting ivars.
Basically, what it has to do, is to establish the class invariant for
the instance. This class invariant is not necessarily set by
-[NSObject init].
Now, when you specify for a class a new designated initializer, this
creates a new initializer for all its subclasses. That is, a client of
the class or any of the subclasses, may and you can assume, will use any
of those initializers.
Therefore, if you have a new designated initializer, it might set up the class invariant, while the other initializers in the super class might
not (probably won't). This is the reason why you need to define
overridden initializers for the non-designated initializer too.
For example, you might have a serialization/deserialization library
(eg. -[NSBundle loadNibNamed:]); there's no mechanism designed to
specify the designated initializer (and it's arguments and valid values)
to recreate the deserialized objects. Instead, the library will just
do: [[class alloc] init] calling the default initializer, and then it
will set the saved ivars directly.
Now, indeed, in this small example, we can assume that -[NSObject init] ensures the Rectangle class invariant (which we may assume to be (0<=width)&&(0<=height)), and calling -[Rectangle init] does nothing
more than -[NSObject init], and therefore as you write, this example
sort of gets in the way of truly understanding the benefit or need of
all of this?
Let's say that a Rectangle shall not be a line or a point:
-(bool)invariant{
return((0<width)&&(0<height));
}
Then:
//Designated initializer
-(id)initWithWidth: (int) w andHeight: (int) h;
{
self = [super init];
if(self){ // always use braces!
// https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-goto-fail-apples-ssl-bug-explained-plus-an-unofficial-patch/
[braces setWidth:max(1,w) andHeight:max(1,h)];
}
assert([self invariant]);
return self;
}
//The override of super's designated initializer
-(id)init{
// smallest rectangle.
return [self initWithWidth:1 andHeight:1];
}
Then it will be obvious that -[NSObject init] doesn't
ensure [self invariant], and that -[Rectangle init] is needed.
You SHOULD use the designated initializer for creating objects of the
class because it was set up to ensure that the object is initialized in
a safe and consistent manner.
However, since your new class may be initialized by some other means,
the basic form of the init should be overridden to make sure that the designated initializer is used to create the object.
Suppose, for instance that you need to create Rectangle instances by
using only the class name using ...
NSObject *myRect = [[NSClassFromString(@"Rectangle") alloc] init];
Ensuring that the init method (which you overrode) calls the designated initializer will make sure that this behaviour "just works".
You SHOULD use the designated initializer for creating objects of the
class because it was set up to ensure that the object is initialized in
a safe and consistent manner.
However, since your new class may be initialized by some other means,
the basic form of the init should be overridden to make sure that the
designated initializer is used to create the object.
Suppose, for instance that you need to create Rectangle instances by
using only the class name using ...
NSObject *myRect = [[NSClassFromString(@"Rectangle") alloc] init];
Ensuring that the init method (which you overrode) calls the designated
initializer will make sure that this behaviour "just works".
Thanks for the response!
I do not understand this example using NSClassFromString that you have provided. In my original post, when I said this was a beginner's
question, I sort of meant it. :-) When using your example:
NSObject *myRect = [[NSClassFromString(@"Rectangle") alloc] init]; an instance of the Rectangle class is not created.
Was there a typo? Did
you mean to state: Rectangle *myRect....??
Also, why is the importance of having overridden init specifically illustrated by this example using NSClassFromString?
Again, I'm assuming that my lack of understanding of the simple
example you have chosen is the reason why I am not understanding your
point.
In article <n2tcmc$t64$1@news.albasani.net>,
Jon Rossen <jonr17@comcast.net> wrote:
But how is this helpful? In my mind it's not helpful for a few
different reasons. Reason 1: In the override to init you've hardwired
the width and height to be 0. That's what NSObject's init would do, so
it's not doing anything new or better. Reason 2: Even if you put
different values in there say, 1 and 2 or whatever values you thought
you might need a newly created Rectangle object to have, they are still
hardwired values, buried inside the newly created init method. How is
this helpful at all? You may want to have a bunch of rectangles of all
different dimensions. At that point you'd have to change their
dimensions with the proper accessor method. If so, why even both with
this newly created init when you can just use NSObject's init and use
the accessor method to tweak the height and width you want each
Rectangle object to be?
Am I correct that the trivial nature of this example sort of gets in the
way of truly understanding the benefit or need of all of this?
thanks,
jonR
In a nutshell, the "designated initializer" is *THE* initializer for a
class that handles *ALL* possibilities for initialization that are
available - Everything else is convenience methods.
Ferinstance:
Assume you've got a class with 5 instance variables, A, B, C, D, and E
Your "designated initializer" should then look something like this:
-(MyClass *)initWithA:(id)a andB:(id)b andC:(id)c andD:(id)d andE:(id)e
{
// I'm ignoring the various checking and calls to super's init for
// brevity, since you seem to understand the chaining mechanism.
// Now stuff the a,b,c,d, and e instance variables with the appropriate
// values that were passed in - either by direct assignment, or via
// accessor methods, as you please - then return self.
}
Now, assuming there's some reason you only need to init an instance
with, ferinstance, a, while all the other instance vars can be left at default values, you could write:
-(MyClass *)initWithJustA:(id)a
{ // (beware line-wrapping for post...)
return [InitWithA:a andB:defaultBValue andC:defaultCValue andD:defaultDValue andE:defaultEValue];
}
Likewise, if you need to init A and B, but C, D, and E can be left at default, you'd write:
-(MyClass *)initWithA:(id)a andB:(id)b
{ // (beware line-wrapping for post...)
return [initWithA:a andB:b andC:defaultCValue andD:defaultDValue andE:defaultEValue];
}
and so on for each combination of initializations that make sense for
your code.
And finally, for an instance of the class that can be left with all of
the vars at default values, you could write:
-(MyClass *)init
{
return [InitWithA:defaultAValue andB:defaultBValue andC:defaultCValue andD:defaultDValue andE:defaultEValue];
}
Notice how the "plain" init calls the designated initializer with
default values for each and every instance variable?
Further note that in the "not designated" initializers, you *DON'T* do
the [super init] checks and calls - You've written the "designated" initializer so that it handles doing that. So long as you call the
designated initializer to do the actual initialization, that stuff gets handled "automagically"
The whole point of the "designated initializer" is that an instance of a class - any class - is expected to be "ready to use" immediately after
being initialized. Using a designated initializer makes certain that any setup an instance needs done gets done, with nothing being forgotten.
I have a rather beginner's basic type of question on designated
initializers. The book that I'm using uses an extremely simple (and
trivial) example (which is usually the case) which leaves me with about
as many questions as answers.
Here's the simple example from the book using a Rectangle class which is
a child class of NSObject.
Here's some of the pertinent info from Rectangle.h; I won't bother
including stuff that's not directly related to the example:
Rectangle.h
@property int width, height;
-(void) setWidth: (int) w andHeight: (int) h;
-(instancetype) initWithWidth: (int) w andHeight: (int) h;
-(instancetype) init;
Ok, here are just the new, custom init methods from Rectangle.m
Rectangle.m
//Designated initializer
-(instancetype) initWithWidth: (int) w andHeight: (int) h;
{
self = [super init];
if (self)
[self setWidth:w andHeight:h];
return self;
}
//The override of super's designated initializer
-(instancetype) init
{
return [self initWithWidth:0 andHeight:0];
}
Ok, I get what's going on here: You create a new designated initializer which handles a lot of the ivars that you feel are important. You have
to include a call super's designated intializer and assign it to self,
then the 'if' test, then your initialization code. You then override
super's designated initializer (in this case it's NSObject's init) and
return a call to your designated initializer. I get how the hierarchy
is supposed to work.
But how is this helpful? In my mind it's not helpful for a few
different reasons. Reason 1: In the override to init you've hardwired
the width and height to be 0. That's what NSObject's init would do, so
it's not doing anything new or better. Reason 2: Even if you put
different values in there say, 1 and 2 or whatever values you thought
you might need a newly created Rectangle object to have, they are still hardwired values, buried inside the newly created init method. How is
this helpful at all? You may want to have a bunch of rectangles of all different dimensions. At that point you'd have to change their
dimensions with the proper accessor method. If so, why even both with
this newly created init when you can just use NSObject's init and use
the accessor method to tweak the height and width you want each
Rectangle object to be?
Am I correct that the trivial nature of this example sort of gets in the
way of truly understanding the benefit or need of all of this?
thanks,
jonR
1. Just a workflow question: Re: the 'convenience' init methods that
may be helpful to get objects accompany the designated one: Is it
typical for a developer when building a class to add the 'convenience'
init methods later on or sort of on an 'as needed' basis depending?
I'm not asking this as to what's good or bad, but am assuming it just
depends on the situation and in your experience how does this usually
play out?
2. Ok, hopefully this question won't open up a can of worms. What
about sub-classes? I guess this is a two part question:
a) If there's a sub-class that doesn't particularly need it's own
class designated initializer, it could just use it's super's
designated initializer, correct? If so, is this a common design
pattern?
b) If the sub-class *does* need a designated initializer, I suppose it
could do an override of the super's version and that would be
ok. However, I would think that any additional initialization would
have to be done via assessor methods as the method names would have to
be the same. Would this be considered bad form to do this? Or would
the best course of action would be to write a new designated
initializer and not do an override?
Last one, slightly unrelated to 2a and 2b but still about
relationships between classes and sub-classes:
c) At each step down the hierarchy through sub-classes, the 'plain'
init would have to be overridden and it would have to return the
current class' designated initializer, correct? In other words, you'd
have to re-write init for each sub-class where there is a new
designated initializer.
Jon Rossen <jonr17@comcast.net> writes:
1. Just a workflow question: Re: the 'convenience' init methods that
may be helpful to get objects accompany the designated one: Is it
typical for a developer when building a class to add the 'convenience'
init methods later on or sort of on an 'as needed' basis depending?
I'm not asking this as to what's good or bad, but am assuming it just
depends on the situation and in your experience how does this usually
play out?
The important criteria is whether the superclass -init establishes the
class invariant or not.
2. Ok, hopefully this question won't open up a can of worms. What
about sub-classes? I guess this is a two part question:
a) If there's a sub-class that doesn't particularly need it's own
class designated initializer, it could just use it's super's
designated initializer, correct? If so, is this a common design
pattern?
You can change the designated initializer for each sub*class.
But since this is not a property checked by the compiler, it would be
very hard for the programmer to track which is the designated
initializer of the current sub*class.
b) If the sub-class *does* need a designated initializer, I suppose it
could do an override of the super's version and that would be
ok. However, I would think that any additional initialization would
have to be done via assessor methods as the method names would have to
be the same. Would this be considered bad form to do this? Or would
the best course of action would be to write a new designated
initializer and not do an override?
There's never a "need" for a specific designated initializer. You can
always either establish the invariant in the -init method (by setting
good default ivars), or extend the invariant to allow for
"half-initilized" instances.
@interface Rectangle
{
int width;
int height;
}
-(BOOL)isValid;
-(BOOL)invariant;
-(void)setWidth:(int)aWidth;
-(void)setHeight:(int)aHeight;
@end
// half-initialized instances:
@implementation Rectangle
-(BOOL)isValid{return((0<width)&&(0<height));}
-(BOOL)invariant{return(![self isValid] || ((0<width)&&(0<height)));}
-(void)setWidth:(int)aWidth{width=aWidth;} // post: ![self isValid] || [self invariant]
-(void)setHeight:(int)aHeight{height=aHeight;}; // post: ![self isValid] || [self invariant]
@end
[[[Rectangle alloc]init]invariant] --> YES
[[[Rectangle alloc]init]isValid] --> NO
// default-initialized instances:
@implementation Rectangle
-(id)init{if((self=[super init])){width=1;height=1;}return(self);}
-(BOOL)isValid{return YES;}
-(BOOL)invariant{return((0<width)&&(0<height));}
-(void)setWidth:(int)aWidth{if(0<aWidth){width=aWidth;}}
// pre: [self invariant] && good=(0<aWidth)
// post: [self invariant] && (good => width==aWidth)
-(void)setHeight:(int)aHeight{if(0<aHeight){height=aHeight;}}
// pre: [self invariant] && good=(0<aHeight)
// post: [self invariant] && (good => height==aHeight)
@end
[[[Rectangle alloc]init]invariant] --> YES
[[[Rectangle alloc]init]isValid] --> YES
Last one, slightly unrelated to 2a and 2b but still about
relationships between classes and sub-classes:
c) At each step down the hierarchy through sub-classes, the 'plain'
init would have to be overridden and it would have to return the
current class' designated initializer, correct? In other words, you'd
have to re-write init for each sub-class where there is a new
designated initializer.
No. If you don't change the designated initializer, then the superclass -init that sends the designated initializer message will invoke the designated initializer method of the current class. You only need to
write a new -init if it has to do something different to ensure the
class invariant.
enum {red,green,blue};
@interface ColoredRectangle
{
int color;
}
@end
@implementation ColoredRectangle
-(id)initWithWidth:(int)w andHeight:(int)h{
if((self=[super initWithWidth:w andHeight:h])){
color=red;
}
return self;
}
-(BOOL)invariant{
return [super invariant]
&& ((color==red)||(color=green)||(color==blue));}
@end
[[[ColoredRectangle alloc]init]invariant] --> YES
[[ColoredRectangle alloc]init]
calls -[Rectangle init]
which sends -initWithWidht:andHeight: to self
which calls -[ColoredRectangle initWithWidht:andHeight:]
which sends -initWithWidht:andHeight: to super
which calls -[Rectangle initWithWidht:andHeight:]
which sends -init to super
which calls -[NSObject init]
Don, Thanks so much for your response with all of this info; I really
appreciate it. It is the perfect combination of theory and practical
info. You are right, I sort of understand the chaining mechanism in how
things unfold in the class hierarchy. The construct that you
illustrated here with the default values used by the 'convenience' init
methods is something I came across earlier today coincidentally, but
your explanation was much more specific, to the point and not obfuscated
by vague implications. I have a few more questions:
1. Just a workflow question: Re: the 'convenience' init methods that
may be helpful to get objects accompany the designated one: Is it
typical for a developer when building a class to add the 'convenience'
init methods later on or sort of on an 'as needed' basis depending? I'm
not asking this as to what's good or bad, but am assuming it just
depends on the situation and in your experience how does this usually
play out?
I can't speak for anyone but myself, so YMMV...
Personally, I code a "designated" initializer that takes appropriate
inputs and uses them to handle *EVERYTHING*, to start with, then a
"naked init" ("-(MyClass *)init") method that passes some reasonable
defaults into the designated initializer. Often, but nowhere near
always, that's all that's needed. (Or more accurately, it's *ALWAYS* all that's *NEEDED*, but it's only occasionally all that's *WANTED*, if you understand the distinction) If, as the project progresses, and I notice
a need for them, I might add more convenience initializers. The decision
to add more convenience initializers or not is totally dependent on the
needs of the specific project - In one project, I might code the
designated initializer, the cover for "init", and several variations, as needed. In another, I might not code anything except the designated initializer, and a cover for "plain" init so that any subclasses I (or someone else...) might cook up later will chain back up the line
properly when hit with a "[super init]". Either way, if somebody
subclasses from your class, they come in with the expectation that doing "[super init]" will do the right thing (by calling the root object's
init method - perhaps directly, or perhaps through a very long and
complex chain of calls, but it'll happen eventually)
2. Ok, hopefully this question won't open up a can of worms. What about
sub-classes? I guess this is a two part question:
a) If there's a sub-class that doesn't particularly need it's own
class designated initializer, it could just use it's super's designated
initializer, correct? If so, is this a common design pattern?
At least in theory, yes, that's correct. As for how common it is...
<shrug> No clue - I've never actually taken the time to think about it.
(minor nitpick: "it's" is a contraction meaning "it is" - You're looking
for the possessive form: "its" - "that which belongs to it". Ain't it wonderful how consistent the english language is? :) )
b) If the sub-class *does* need a designated initializer, I suppose it
could do an override of the super's version and that would be ok.
However, I would think that any additional initialization would have to
be done via assessor methods as the method names would have to be the
same.
Sounds right... I THINK - Assuming you're asking what I think you are.
If you're expecting to be subclassing off your subclass, then write your "first level" subclass - let's call it MyClass - like so:
(I'm assuming you're on a Mac, so you'll probably be subclassing it from NSObject)
Your .h file has (at least - perhaps MUCH more) in it:
---cut---
#import <Cocoa/Cocoa.h>
@interface MyClass : NSObject
{
// MyClass specific vars
NSDictionary *myClassVars;
}
// And some method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
// More stuff will likely live here in a useful program.
@end
---cut---
And in your .m file:
---cut---
#import "MyClass.h"
@implementation MyClass
-(MyClass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invoke NSObject's init
// Do MyClass-specific init
// and return what self has become.
return self;
}
-(MyClass *)init
{
// I'll assume defaultParams is set appropriately elsewhere
return [self designatedInitializer:defaultParams];
}
// Any other methods MyClass needs follow
@end
---cut---
Notice that all init does is call the instance's designated initializer
with default params. Which in turn calls [super init] (NSObject's, in
this case) before using the parameters it gets passed. You get the rest
of your MyClass going, ship the project, and make a few bucks.
Time passes, and a project you just got handed wants something very
nearly identical to MyClass, but with a couple little additions and
tweaks. You decide to subclass from MyClass to make a new class of
object - The MySubclass
You toss together a quick .h file:
---cut---
#import "MyClass.h"
@interface MySublass : MyClass
{
// MySublass specific vars
NSDictionary *SubclassVar;
}
// And don't forget the method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
@end
---cut---
And the .m file
---cut---
#import "MySubclass.h"
-(MySubclass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invokes MyClass' init
// Do MySubclass-specific init work
// and return what self has become.
return self;
}
-(MySubclass *)init
{
// I'll assume defaultMySubclassParams is set appropriately somewhere
return [self designatedInitializer:defaultMySubclassParams];
}
// Any other methods MySubclass needs follow
@end
---cut---
You'll notice how close to identical the code is for both cases - The critical consideration is that you ALWAYS want your class' "init" to
chain back to the root object, whether directly or indirectly.
c) At each step down the hierarchy through sub-classes, the 'plain' init
would have to be overridden and it would have to return the current
class' designated initializer, correct? In other words, you'd have to
re-write init for each sub-class where there is a new designated
initializer.
Basically, yes. See the snippets above.
On 11/22/2015 7:15 PM, Don Bruder wrote:
In article <n2tcmc$t64$1@news.albasani.net>,
Jon Rossen <jonr17@comcast.net> wrote:
But how is this helpful? In my mind it's not helpful for a few
different reasons. Reason 1: In the override to init you've hardwired
the width and height to be 0. That's what NSObject's init would do, so
it's not doing anything new or better. Reason 2: Even if you put
different values in there say, 1 and 2 or whatever values you thought
you might need a newly created Rectangle object to have, they are still
hardwired values, buried inside the newly created init method. How is
this helpful at all? You may want to have a bunch of rectangles of all
different dimensions. At that point you'd have to change their
dimensions with the proper accessor method. If so, why even both with
this newly created init when you can just use NSObject's init and use
the accessor method to tweak the height and width you want each
Rectangle object to be?
Am I correct that the trivial nature of this example sort of gets in the >> way of truly understanding the benefit or need of all of this?
thanks,
jonR
In a nutshell, the "designated initializer" is *THE* initializer for a class that handles *ALL* possibilities for initialization that are available - Everything else is convenience methods.
Ferinstance:
Assume you've got a class with 5 instance variables, A, B, C, D, and E
Your "designated initializer" should then look something like this:
-(MyClass *)initWithA:(id)a andB:(id)b andC:(id)c andD:(id)d andE:(id)e
{
// I'm ignoring the various checking and calls to super's init for
// brevity, since you seem to understand the chaining mechanism.
// Now stuff the a,b,c,d, and e instance variables with the appropriate
// values that were passed in - either by direct assignment, or via
// accessor methods, as you please - then return self.
}
Now, assuming there's some reason you only need to init an instance
with, ferinstance, a, while all the other instance vars can be left at default values, you could write:
-(MyClass *)initWithJustA:(id)a
{ // (beware line-wrapping for post...)
return [InitWithA:a andB:defaultBValue andC:defaultCValue andD:defaultDValue andE:defaultEValue];
}
Likewise, if you need to init A and B, but C, D, and E can be left at default, you'd write:
-(MyClass *)initWithA:(id)a andB:(id)b
{ // (beware line-wrapping for post...)
return [initWithA:a andB:b andC:defaultCValue andD:defaultDValue andE:defaultEValue];
}
and so on for each combination of initializations that make sense for
your code.
And finally, for an instance of the class that can be left with all of
the vars at default values, you could write:
-(MyClass *)init
{
return [InitWithA:defaultAValue andB:defaultBValue andC:defaultCValue andD:defaultDValue andE:defaultEValue];
}
Notice how the "plain" init calls the designated initializer with
default values for each and every instance variable?
Further note that in the "not designated" initializers, you *DON'T* do
the [super init] checks and calls - You've written the "designated" initializer so that it handles doing that. So long as you call the designated initializer to do the actual initialization, that stuff gets handled "automagically"
The whole point of the "designated initializer" is that an instance of a class - any class - is expected to be "ready to use" immediately after being initialized. Using a designated initializer makes certain that any setup an instance needs done gets done, with nothing being forgotten.
Don, Thanks so much for your response with all of this info; I really appreciate it. It is the perfect combination of theory and practical
info. You are right, I sort of understand the chaining mechanism in how things unfold in the class hierarchy. The construct that you
illustrated here with the default values used by the 'convenience' init methods is something I came across earlier today coincidentally, but
your explanation was much more specific, to the point and not obfuscated
by vague implications. I have a few more questions:
1. Just a workflow question: Re: the 'convenience' init methods that
may be helpful to get objects accompany the designated one: Is it
typical for a developer when building a class to add the 'convenience'
init methods later on or sort of on an 'as needed' basis depending? I'm
not asking this as to what's good or bad, but am assuming it just
depends on the situation and in your experience how does this usually
play out?
2. Ok, hopefully this question won't open up a can of worms. What about sub-classes? I guess this is a two part question:
a) If there's a sub-class that doesn't particularly need it's own
class designated initializer, it could just use it's super's designated initializer, correct? If so, is this a common design pattern?
b) If the sub-class *does* need a designated initializer, I suppose it
could do an override of the super's version and that would be ok.
However, I would think that any additional initialization would have to
be done via assessor methods as the method names would have to be the
same.
c) At each step down the hierarchy through sub-classes, the 'plain' init would have to be overridden and it would have to return the current
class' designated initializer, correct? In other words, you'd have to re-write init for each sub-class where there is a new designated
initializer.
Ok....but backing up to the beginning of your paragraph:
Hmmm, you lost me when you brought up your 'naked init' and said that
it 'passes some reasonable defaults into the designated initializer'.
The naked init in the current class just calls the current class'
designated initializer and returns it. How does it pass some
reasonable defaults *into it*??
Might you be referring to the naked init that is the *superclass* of
the current class? Certainly the super's init does that when you do
self = [super init]. I just don't understand how the init at the
current level feeds anything into the designated initializer at that
same level.
Speaking of nitpicks, in your code examples why don't you use
(instancetype) as your return type?
You'll notice how close to identical the code is for both cases - The
critical consideration is that you ALWAYS want your class' "init" to
chain back to the root object, whether directly or indirectly.
Not only are they close, aren't they basically *the exact same code* structurally? Meaning apart from the class-specific stuff that would
be needed (different name of designated initializer, additional
parameters, etc., etc.), the framework here is exactly the same as it
was for the subclass (MyClass). I would *expect* it to be the same
seeing that it's keeping the init chaining intact. Am I missing
something or am I 'getting it'?
I don't agree with Don's code.
In general the subclass should call:
[super designatedInitializer:argument]
and not [super init], in its -designatedInitializer: method.
Notably, when the argument is given as a parameter to the subclass -designatedInitializer:!
---cut---
#import <Cocoa/Cocoa.h>
@interface MyClass : NSObject
{
// MyClass specific vars
NSDictionary *myClassVars;
}
// And some method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
// More stuff will likely live here in a useful program.
@end
---cut---
And in your .m file:
---cut---
#import "MyClass.h"
@implementation MyClass
-(MyClass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invoke NSObject's init
// Do MyClass-specific init
// and return what self has become.
return self;
}
-(MyClass *)init
{
// I'll assume defaultParams is set appropriately elsewhere
return [self designatedInitializer:defaultParams];
}
// Any other methods MyClass needs follow
@end
---cut---
---cut---
#import "MyClass.h"
@interface MySublass : MyClass
{
// MySublass specific vars
NSDictionary *SubclassVar;
}
// And don't forget the method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
@end
---cut---
And the .m file
---cut---
#import "MySubclass.h"
-(MySubclass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invokes MyClass' init
// Do MySubclass-specific init work
// and return what self has become.
return self;
}
-(MySubclass *)init
{
// I'll assume defaultMySubclassParams is set appropriately somewhere
return [self designatedInitializer:defaultMySubclassParams];
}
// Any other methods MySubclass needs follow
@end
---cut---
On 11/23/2015 7:58 PM, Pascal J. Bourguignon wrote:
Jon Rossen <jonr17@comcast.net> writes:
Last one, slightly unrelated to 2a and 2b but still about
relationships between classes and sub-classes:
c) At each step down the hierarchy through sub-classes, the 'plain'
init would have to be overridden and it would have to return the
current class' designated initializer, correct? In other words, you'd
have to re-write init for each sub-class where there is a new
designated initializer.
No. If you don't change the designated initializer, then the superclass
-init that sends the designated initializer message will invoke the
designated initializer method of the current class. You only need to
write a new -init if it has to do something different to ensure the
class invariant.
I'm not sure whether I wasn't clear in how I posed the question and
you are misunderstanding me, or I'm not understanding your answer. I
asked if you had to rewrite init where there is a new designated
initializer, meaning that you *HAD* to re-write init. Conversely,
when I posed the question, my assumption was that if there was NOT a
new designated initializer then init would not have to be
rewritten...which is what you seem to be saying here. So, I'm not
sure why you started your answer with a 'No': It either could be that
I wasn't clear in my question OR you did understand the question and
I'm not understanding a fine point you are making.
[[ColoredRectangle alloc]init]This makes sense, at least the way the method calls work with each
calls -[Rectangle init]
which sends -initWithWidht:andHeight: to self
which calls -[ColoredRectangle initWithWidht:andHeight:]
which sends -initWithWidht:andHeight: to super
which calls -[Rectangle initWithWidht:andHeight:]
which sends -init to super
which calls -[NSObject init]
other. Did you show this *just* to show the chain of events? Or, did
you mean it to represent a workflow, in how these methods would be
used? That's the part that doesn't make sense (using init). You are starting off by calling init, (which calls Rectangle's init) and sets
of this chain of events. However, you've lost your opportunity to
directly use the designated initializer and pass arguments to it.
Wouldn't you just use the designated initializer in the sub-class (ColoredRectangle) just as it would be used in the parent class
(Rectangle)?
< A bunch of previous stuff cut out...new comments inline below>
thx -jonR
Don, Thanks so much for your response with all of this info; I really
appreciate it. It is the perfect combination of theory and practical
info. You are right, I sort of understand the chaining mechanism in how >> things unfold in the class hierarchy. The construct that you
illustrated here with the default values used by the 'convenience' init
methods is something I came across earlier today coincidentally, but
your explanation was much more specific, to the point and not obfuscated >> by vague implications. I have a few more questions:
1. Just a workflow question: Re: the 'convenience' init methods that
may be helpful to get objects accompany the designated one: Is it
typical for a developer when building a class to add the 'convenience'
init methods later on or sort of on an 'as needed' basis depending? I'm >> not asking this as to what's good or bad, but am assuming it just
depends on the situation and in your experience how does this usually
play out?
I can't speak for anyone but myself, so YMMV...
Personally, I code a "designated" initializer that takes appropriate
inputs and uses them to handle *EVERYTHING*, to start with, then a
"naked init" ("-(MyClass *)init") method that passes some reasonable defaults into the designated initializer. Often, but nowhere near
always, that's all that's needed. (Or more accurately, it's *ALWAYS* all that's *NEEDED*, but it's only occasionally all that's *WANTED*, if you understand the distinction) If, as the project progresses, and I notice
a need for them, I might add more convenience initializers. The decision
to add more convenience initializers or not is totally dependent on the needs of the specific project - In one project, I might code the
designated initializer, the cover for "init", and several variations, as needed. In another, I might not code anything except the designated initializer, and a cover for "plain" init so that any subclasses I (or someone else...) might cook up later will chain back up the line
properly when hit with a "[super init]". Either way, if somebody
subclasses from your class, they come in with the expectation that doing "[super init]" will do the right thing (by calling the root object's
init method - perhaps directly, or perhaps through a very long and
complex chain of calls, but it'll happen eventually)
Ok....but backing up to the beginning of your paragraph:
Hmmm, you lost me when you brought up your 'naked init' and said that it 'passes some reasonable defaults into the designated initializer'. The
naked init in the current class just calls the current class' designated initializer and returns it. How does it pass some reasonable defaults
*into it*?? Might you be referring to the naked init that is the *superclass* of the current class? Certainly the super's init does that
when you do self = [super init]. I just don't understand how the init
at the current level feeds anything into the designated initializer at
that same level.
2. Ok, hopefully this question won't open up a can of worms. What about >> sub-classes? I guess this is a two part question:
a) If there's a sub-class that doesn't particularly need it's own
class designated initializer, it could just use it's super's designated
initializer, correct? If so, is this a common design pattern?
At least in theory, yes, that's correct. As for how common it is...
<shrug> No clue - I've never actually taken the time to think about it.
(minor nitpick: "it's" is a contraction meaning "it is" - You're looking for the possessive form: "its" - "that which belongs to it". Ain't it wonderful how consistent the english language is? :) )
Yeah, yeah, I know all of that; it was purely a typo due to being tired.
When I get tired I have a tendency to spell fenetically :-) and little details drop off by the wayside. But great nitpick...I'd give it a 9
out of 10 at least. :-)
Speaking of nitpicks, in your code examples why don't you use
(instancetype) as your return type?
b) If the sub-class *does* need a designated initializer, I suppose it
could do an override of the super's version and that would be ok.
However, I would think that any additional initialization would have to
be done via assessor methods as the method names would have to be the
same.
Sounds right... I THINK - Assuming you're asking what I think you are.
OK.
If you're expecting to be subclassing off your subclass, then write your "first level" subclass - let's call it MyClass - like so:
(I'm assuming you're on a Mac, so you'll probably be subclassing it from NSObject)
Your .h file has (at least - perhaps MUCH more) in it:
---cut---
#import <Cocoa/Cocoa.h>
@interface MyClass : NSObject
{
// MyClass specific vars
NSDictionary *myClassVars;
}
// And some method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
// More stuff will likely live here in a useful program.
@end
---cut---
And in your .m file:
---cut---
#import "MyClass.h"
@implementation MyClass
-(MyClass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invoke NSObject's init
// Do MyClass-specific init
// and return what self has become.
return self;
}
-(MyClass *)init
{
// I'll assume defaultParams is set appropriately elsewhere
return [self designatedInitializer:defaultParams];
}
// Any other methods MyClass needs follow
@end
---cut---
Notice that all init does is call the instance's designated initializer with default params. Which in turn calls [super init] (NSObject's, in
this case) before using the parameters it gets passed. You get the rest
of your MyClass going, ship the project, and make a few bucks.
Right. This code example is pretty much the same example you showed me
the other day, and BTW, is consistent with my understanding of how to do this. So, there's nothing new here with respect to the other day's
example, right?
Time passes, and a project you just got handed wants something very
nearly identical to MyClass, but with a couple little additions and
tweaks. You decide to subclass from MyClass to make a new class of
object - The MySubclass
You toss together a quick .h file:
---cut---
#import "MyClass.h"
@interface MySublass : MyClass
{
// MySublass specific vars
NSDictionary *SubclassVar;
}
// And don't forget the method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
@end
---cut---
And the .m file
---cut---
#import "MySubclass.h"
-(MySubclass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invokes MyClass' init
// Do MySubclass-specific init work
// and return what self has become.
return self;
}
-(MySubclass *)init
{
// I'll assume defaultMySubclassParams is set appropriately somewhere
return [self designatedInitializer:defaultMySubclassParams];
}
// Any other methods MySubclass needs follow
@end
---cut---
You'll notice how close to identical the code is for both cases - The critical consideration is that you ALWAYS want your class' "init" to
chain back to the root object, whether directly or indirectly.
Not only are they close, aren't they basically *the exact same code* structurally? Meaning apart from the class-specific stuff that would be needed (different name of designated initializer, additional parameters, etc., etc.), the framework here is exactly the same as it was for the subclass (MyClass). I would *expect* it to be the same seeing that it's keeping the init chaining intact. Am I missing something or am I
'getting it'?
c) At each step down the hierarchy through sub-classes, the 'plain' init >> would have to be overridden and it would have to return the current
class' designated initializer, correct? In other words, you'd have to
re-write init for each sub-class where there is a new designated
initializer.
Basically, yes. See the snippets above.
Yep, my above two comments discusses this.
thanks for your response.
-jonR
Don Bruder <dakidd@sonic.net> writes:
In article <87oaeivr7a.fsf@kuiper.lan.informatimago.com>,
"Pascal J. Bourguignon" <pjb@informatimago.com> wrote:
I don't agree with Don's code.
In general the subclass should call:
[super designatedInitializer:argument]
and not [super init], in its -designatedInitializer: method.
Notably, when the argument is given as a parameter to the subclass
-designatedInitializer:!
OK, now *I'M* confused... Lemme go look at the code...
Now that I've gone and looked, I'm gonna update that to "I'm COMPLETELY
lost!" Please snatch in a copy of the code you're talking about and
annotate it to tell me specifically what it is you consider wrong with
it. I can't see anything (which is not to say it's impossible for me to
be wrong - only that if I am, I'm not seeing how or where based on what
you said), and I can't parse what you're saying up there in any way that
seems to make sense.
Never mind snatching it in... I've got it here, and I still can't see
what might be wrong.
???
---cut---
#import <Cocoa/Cocoa.h>
@interface MyClass : NSObject
{
// MyClass specific vars
NSDictionary *myClassVars;
}
// And some method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
// More stuff will likely live here in a useful program.
@end
---cut---
And in your .m file:
---cut---
#import "MyClass.h"
@implementation MyClass
-(MyClass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invoke NSObject's init
// Do MyClass-specific init
// and return what self has become.
return self;
}
-(MyClass *)init
{
// I'll assume defaultParams is set appropriately elsewhere
return [self designatedInitializer:defaultParams];
}
// Any other methods MyClass needs follow
@end
---cut---
---cut---
#import "MyClass.h"
@interface MySublass : MyClass
{
// MySublass specific vars
NSDictionary *SubclassVar;
}
// And don't forget the method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
@end
---cut---
And the .m file
---cut---
#import "MySubclass.h"
-(MySubclass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invokes MyClass' init
This. The designated initializer of the super class (ie. the MyClass
class), is designatedInitializer: ; therefore this is the designated initializer you must call from the subclass.
Since you call [super init], -[MyClass init] will send the -designatedInitializer: message to self, which will call
-[MySubclass designatedInitializer:] and this will give an infinite
loop.
// Do MySubclass-specific init work
// and return what self has become.
return self;
}
-(MySubclass *)init
{
// I'll assume defaultMySubclassParams is set appropriately somewhere >>> return [self designatedInitializer:defaultMySubclassParams];
}
// Any other methods MySubclass needs follow
@end
---cut---
Jon Rossen <jonr17@comcast.net> writes:
2. Ok, hopefully this question won't open up a can of worms. What
about sub-classes? I guess this is a two part question:
a) If there's a sub-class that doesn't particularly need it's own
class designated initializer, it could just use it's super's
designated initializer, correct? If so, is this a common design
pattern?
You can change the designated initializer for each sub*class.
But since this is not a property checked by the compiler, it would be
very hard for the programmer to track which is the designated
initializer of the current sub*class.
In article <87oaeivr7a.fsf@kuiper.lan.informatimago.com>,
"Pascal J. Bourguignon" <pjb@informatimago.com> wrote:
I don't agree with Don's code.
In general the subclass should call:
[super designatedInitializer:argument]
and not [super init], in its -designatedInitializer: method.
Notably, when the argument is given as a parameter to the subclass
-designatedInitializer:!
OK, now *I'M* confused... Lemme go look at the code...
Now that I've gone and looked, I'm gonna update that to "I'm COMPLETELY lost!" Please snatch in a copy of the code you're talking about and
annotate it to tell me specifically what it is you consider wrong with
it. I can't see anything (which is not to say it's impossible for me to
be wrong - only that if I am, I'm not seeing how or where based on what
you said), and I can't parse what you're saying up there in any way that seems to make sense.
Never mind snatching it in... I've got it here, and I still can't see
what might be wrong.
???
---cut---
#import <Cocoa/Cocoa.h>
@interface MyClass : NSObject
{
// MyClass specific vars
NSDictionary *myClassVars;
}
// And some method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
// More stuff will likely live here in a useful program.
@end
---cut---
And in your .m file:
---cut---
#import "MyClass.h"
@implementation MyClass
-(MyClass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invoke NSObject's init
// Do MyClass-specific init
// and return what self has become.
return self;
}
-(MyClass *)init
{
// I'll assume defaultParams is set appropriately elsewhere
return [self designatedInitializer:defaultParams];
}
// Any other methods MyClass needs follow
@end
---cut---
---cut---
#import "MyClass.h"
@interface MySublass : MyClass
{
// MySublass specific vars
NSDictionary *SubclassVar;
}
// And don't forget the method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
@end
---cut---
And the .m file
---cut---
#import "MySubclass.h"
-(MySubclass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invokes MyClass' init
// Do MySubclass-specific init work
// and return what self has become.
return self;
}
-(MySubclass *)init
{
// I'll assume defaultMySubclassParams is set appropriately somewhere
return [self designatedInitializer:defaultMySubclassParams];
}
// Any other methods MySubclass needs follow
@end
---cut---
You'll pardon my skepticism, I hope - I'm an empiricist at heart...
Gonna do some playing and see what I can come up with, but at this
stage, what you're saying is so *TOTALLY* counterintuitive I can't even
start to accept it. Everything I've ever grasped about inheritance/subclassing tells me that I'm not supposed to "need" to know
the internals of the class I'm deriving from - it's a black box. So long
as you do thus and so, your subclass will work. "Thus and so" in this
case meaning "Make damn sure you chain back to the root object by
calling your superclass' init method or all hell's gonna break loose".
Don Bruder <dakidd@sonic.net> writes:
In article <87oaeivr7a.fsf@kuiper.lan.informatimago.com>,
"Pascal J. Bourguignon" <pjb@informatimago.com> wrote:
I don't agree with Don's code.
In general the subclass should call:
[super designatedInitializer:argument]
and not [super init], in its -designatedInitializer: method.
Notably, when the argument is given as a parameter to the subclass
-designatedInitializer:!
OK, now *I'M* confused... Lemme go look at the code...
Now that I've gone and looked, I'm gonna update that to "I'm COMPLETELY lost!" Please snatch in a copy of the code you're talking about and annotate it to tell me specifically what it is you consider wrong with
it. I can't see anything (which is not to say it's impossible for me to
be wrong - only that if I am, I'm not seeing how or where based on what
you said), and I can't parse what you're saying up there in any way that seems to make sense.
Never mind snatching it in... I've got it here, and I still can't see
what might be wrong.
???
---cut---
#import <Cocoa/Cocoa.h>
@interface MyClass : NSObject
{
// MyClass specific vars
NSDictionary *myClassVars;
}
// And some method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
// More stuff will likely live here in a useful program.
@end
---cut---
And in your .m file:
---cut---
#import "MyClass.h"
@implementation MyClass
-(MyClass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invoke NSObject's init
// Do MyClass-specific init
// and return what self has become.
return self;
}
-(MyClass *)init
{
// I'll assume defaultParams is set appropriately elsewhere
return [self designatedInitializer:defaultParams];
}
// Any other methods MyClass needs follow
@end
---cut---
---cut---
#import "MyClass.h"
@interface MySublass : MyClass
{
// MySublass specific vars
NSDictionary *SubclassVar;
}
// And don't forget the method declarations
-(MyClass *)designatedInitializer:(id)anyNeededParams;
-(MyClass *)init;
@end
---cut---
And the .m file
---cut---
#import "MySubclass.h"
-(MySubclass *)designatedInitializer:(id)anyNeededParams
{
self = [super init]; // Invokes MyClass' init
This. The designated initializer of the super class (ie. the MyClass
class), is designatedInitializer: ; therefore this is the designated initializer you must call from the subclass.
Since you call [super init], -[MyClass init] will send the -designatedInitializer: message to self, which will call
-[MySubclass designatedInitializer:] and this will give an infinite
loop.
Pascal, I also noticed that you and Don had different code for the
designated initializer and forgot to ask about it; thanks for bringing
it up.
Don uses: self = [super init];
while you use: self = [super designatedInitializer];
I'm not sure if I'm following your code path here. If someone uses
Don's method, [super init] is called, and it returns a
designatedInitializer message to self. Then you lose me when you say
which will call [MySubClass designatedInitializer]' which as you say
will give an infinite loop. I don't understand the call to
[MySubClass designatedInitializer].
So you can have two methods:
-[MyClass designatedInitializer:]
-[MySubClass designatedInitializer:]
Ok, got it. Just a nitpick...these are instance methods, not class
methods so technically shouldn't this example be something like: [myClassObject designatedInitializer]
[mySubclassObject designatedInitializer]
...or does the '-' you have as a prefix serve to state this and the
examples (names used) are supposed to be taken abstractly?
and when you send a designatedInitializer: message:
[self designatedInitializer:42]
one or the other method may be called first, depending on the class of
self.
The version that is called first is the version that is in self's
class. Please tell me that what I have said is correct, or else I'm
going to kill myself after I destroy my computer. :-)
The class of self is not necessarily the class of the method being
executed!
This statement seems at odds at the one right above that I commented
on. What are you saying here? You seem to be saying that the class of
self may not be the class of the method being executed.
But I think I
know why you are saying this. You seem to be implying something here
but you are not explicitly giving a specific scenario.
Is this statement related to the fact that the search for the method
executed first could be in *super's* class?
This is what you touched on a few comments above. In other words in
that scenario (if I'm understanding that correctly), a different
method than the one in self's class would be executed. Is this what
you are saying here?
- if there is no method for the selector in that class, then search in
the superclass, and so on.
When we send a message to super, we start the search directly from the superclass, instead of starting from the class.
So when a method is found, such as -[SomeClass designatedInitializer:]
then it is called. A method is basically a C function with two
additionnal parameters (a selector _cmd, and the recipient self).
Notice that methods are denoted by:
- a sign - for instance methods (stored in the class), + for class
methods (stored in the metaclass).
- a class name,
- a designator.
-[SomeClass designatedInitializer:]
So you can have two methods:
-[MyClass designatedInitializer:]
-[MySubClass designatedInitializer:]
and when you send a designatedInitializer: message:
[self designatedInitializer:42]
one or the other method may be called first, depending on the class of
self.
The class of self is not necessarily the class of the method being
executed!
Usually, the class of self is a SUBCLASS of the class of the method
being executed.
The problem with Don's code is that there are four methods:
-[MyClass init]
[self designatedInitializer:default]
-[MyClass designatedInitializer:]
…
-[MySubClass init]
[super init]
-[MySubClass designatedInitializer:]
[super designatedInitializer:default]
[MySubclass alloc] returns an instance of MySubClass.
The only instance we'll have is of the class MySubClass.
Sending the message [[MySubclass alloc] init]
sends the init message to an instance of the class MySubClass.
So we call the method -[MySubClass init], passing the instance of the
class MySubClass.
which sends the message init to super, that is, to the same instance of
the class MySubClass, but searching the method -init starting from the superclass.
So we call the method -[MyClass init], passing the instance of the
class MySubClass.
This sends the designatedInitializer: message to this instance of the
class MySubClass.
So we search a -designatedInitializer: method starting from the class MySubClass, and we find -[MySubClass designatedInitializer:]
This sends the message [super init], so we search an -init method from
the superclass and find -[MyClass init], passing the instance of the
class MySubClass.
This sends the designatedInitializer: message to this instance of the
class MySubClass.
So we search a -designatedInitializer: method starting from the class MySubClass, and we find -[MySubClass designatedInitializer:]
This sends the message [super init], so we search an -init method from
the superclass and find -[MyClass init], passing the instance of the
class MySubClass.
This sends the designatedInitializer: message to this instance of the
class MySubClass.
So we search a -designatedInitializer: method starting from the class MySubClass, and we find -[MySubClass designatedInitializer:]
This sends the message [super init], so we search an -init method from
the superclass and find -[MyClass init], passing the instance of the
class MySubClass.
This sends the designatedInitializer: message to this instance of the
class MySubClass.
So we search a -designatedInitializer: method starting from the class MySubClass, and we find -[MySubClass designatedInitializer:]
This sends the message [super init], so we search an -init method from
the superclass and find -[MyClass init],
etc.
The whole point of OOP and inheritance, is to be able send a message to
an object without knowing at compilation time what class or subclass
this object will be. So the actual method called when we send this
message, is determined at run-time, and may be a method of a subclass
that wasn't even imagined when we wrote this message sending.
When we have:
@implementation MyClass
-(id)init{
return [self designatedInitializer:42];
}
@end
the only raison-d'être of this method, is to call the method of the
subclass of MyClass of which self is an instance.
Because we know, when we write MyClass, that we don't know what the
future has in stock for us. We know that the customer will come with
some crazy requirements, which makes -[MyClass designatedInitializer:] completely inadapted and outdated.
Therefore when the crazy customer comes with his crazy requirements, we
will write a subclass that will do the right thing, by implementing a -[CrazyCustomerSubclass designatedInitializer:] method, knowing that the
rest of the MyClass code will still work, because everytime it has to
rely on some specific behavior, it will send a message to the object
that will be dispatched to the right method according to the class of
that object.
Jon Rossen <jonr17@comcast.net> writes:
Thanks for the explanations and to be honest I don't fully understand
all of what you have said, but I understand it enough for it to make
sense. However, I realize I need to understand class invariants which
is a new concept to me. I think that is the primary component or
background for me that is lacking and when I get a better
understanding I'll fully understand your explanation.
Have a look at:
https://en.wikipedia.org/wiki/Liskov_substitution_principle
Jon Rossen <jonr17@comcast.net> writes:
On 11/22/2015 5:02 PM, Pascal J. Bourguignon wrote:
Jon Rossen <jonr17@comcast.net> writes:
Thanks for the explanations and to be honest I don't fully understand
all of what you have said, but I understand it enough for it to make
sense. However, I realize I need to understand class invariants which >>>> is a new concept to me. I think that is the primary component or
background for me that is lacking and when I get a better
understanding I'll fully understand your explanation.
Have a look at:
https://en.wikipedia.org/wiki/Liskov_substitution_principle
Geez, interesting but did you really think someone who calls himself a
relative beginner that stated he needed help understanding class
invariants could learn about them by reading this?
Of course not. This is just a pointer to search for further references.
The newbie shall find a book or some on-line course about the subject.
You almost need to know all about class invariants *first* in order to
understand this Wikipedia article. Around the time I would start to
sort things out I'd get another obscure set of factoids and jargon
thrown at me that would create a new set of uncertainty and eventually
I spiraled down the rabbit hole of confusion scratching my head and
saying WTF! :-) I'm sure with a few Google searches I may come up
with some other stuff to read and perhaps with all of them it will
start to become clear. Thanks for at least taking the time to try and
get me on the correct path.
Also I would tend to assume a CS formation for newbie programmers.
Granted this is probably an error, and also it looks like a lot of universities or programming schools fail to teach anything to students
worth the money they paiy for it, but I fail to see how one could gain the ability of programming without having it.
Also, specifically about invariants, pre-/post-conditions, and in
general Hoare logic and program proofs, this is not something specific to OOP. It should have been learned along with the procedural programming languages. Actually, OO introduces indeed some complexities, of which
the Lyskov's substitution principle is the least, and (purely) functional programming also does better with a different variant, given that it
avoids mutation. Lyskov's substitution principle is but a trivial application of Hoare logic in the context of inheritance.
On 11/22/2015 5:02 PM, Pascal J. Bourguignon wrote:
Jon Rossen <jonr17@comcast.net> writes:
Thanks for the explanations and to be honest I don't fully understand
all of what you have said, but I understand it enough for it to make
sense. However, I realize I need to understand class invariants which
is a new concept to me. I think that is the primary component or
background for me that is lacking and when I get a better
understanding I'll fully understand your explanation.
Have a look at:
https://en.wikipedia.org/wiki/Liskov_substitution_principle
Geez, interesting but did you really think someone who calls himself a relative beginner that stated he needed help understanding class
invariants could learn about them by reading this?
You almost need to know all about class invariants *first* in order to understand this Wikipedia article. Around the time I would start to
sort things out I'd get another obscure set of factoids and jargon
thrown at me that would create a new set of uncertainty and eventually
I spiraled down the rabbit hole of confusion scratching my head and
saying WTF! :-) I'm sure with a few Google searches I may come up
with some other stuff to read and perhaps with all of them it will
start to become clear. Thanks for at least taking the time to try and
get me on the correct path.
There was one simple example given that discussed a Rectangle class
that had a Square sub-class. Obviously, in Geometry a square is a
rectangle but a rectangle is not a square. The programming issue that
was brought up was basically an outgrowth of this subset/superset relationship involving the setters needed to set the dimension of the objects. For rectangles you'd need height and width but for squares
you really only need one, and furthermore having two for the square
would not really be correct contextually. These seem like expected
inherent differences in these two classes, but I was getting mixed up following the article and whether it was suggesting that this
condition between these two classes were following LSP or 'breaking'
it. Basically I got lost trying to fully understand how this example
was being used. Perhaps I'll reread that specific section and you as Professor Bourguignon can fill me in with answers if I can figure out
how to craft some specific questions. :-)
the post-condition of -[Rectangle setWidth:]
-[Rectangle invariant]
Don Bruder <dakidd@sonic.net> writes:
You'll pardon my skepticism, I hope - I'm an empiricist at heart...
I assumed so much, and therefore I assumed you already ran the code, and
saw the infinite loop for yourself!
Gonna do some playing and see what I can come up with, but at this
stage, what you're saying is so *TOTALLY* counterintuitive I can't even start to accept it. Everything I've ever grasped about inheritance/subclassing tells me that I'm not supposed to "need" to know the internals of the class I'm deriving from - it's a black box. So long
as you do thus and so, your subclass will work. "Thus and so" in this
case meaning "Make damn sure you chain back to the root object by
calling your superclass' init method or all hell's gonna break loose".
Well, either your class is "documented", with a class invariant
and a set of pre- and post- conditions for each method, or you have to
know the internals.
Since you never find those invariants and sets of pre- and
post-conditions documented, you never know, and always have to know the internals, which indeed, is a very bad situation.
Well, either your class is "documented", with a class invariant
and a set of pre- and post- conditions for each method, or you have to
know the internals.
Since you never find those invariants and sets of pre- and
post-conditions documented, you never know, and always have to know the
internals, which indeed, is a very bad situation.
Yeah... it does a serious number on the whole "A class stands alone, you
can treat it as a black box" concept that was (until now...) my
understanding of one of the main points of OOP.
So now, I'm gonna have to get my head around how to cope with this "breakage"...
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 296 |
Nodes: | 16 (2 / 14) |
Uptime: | 60:03:57 |
Calls: | 6,654 |
Calls today: | 6 |
Files: | 12,200 |
Messages: | 5,331,392 |