Initializing a class with designated initializer - confusion - objective-c

I am reading a book which teaches that we should implement a designated initializer of class, and then let other initializers call this one (which I agree with).
Now, I am in a section in this book, which says: "The designated initializer of UITableViewController is initWithStyle:". And then goes in the implementation like this:
// inside ItemsViewController.m, subclass of UITableViewController
-(id) init{
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
}
return self;
}
- (id)initWithStyle:(UITableViewStyle)style
{
return [self init];
}
After this code, the book also says: "This will ensure that all instances of ItemsViewController use the UITableViewStyleGrouped style, no matter what initialization message is sent to it."
To stick to the principle I described in the beginning of the post, I might had implemented this class like this:
// inside ItemsViewController.m, subclass of UITableViewController
// Implement the designated initializer first
-(id) initWithStyle:(UITableViewStyle)style
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
}
return self;
}
// Call designated initializer
- (id)init
{
return [self initWithStyle: nil];
}
But I think I am missing something? I think the book uses init as designated initializer in their case?

Construction
// Implement the designated initializer first
-(id) initWithStyle:(UITableViewStyle)style
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
}
return self; }
is confusing for user of class, because he will think that you provided initWithStyle constructor to allow him setting controllers' style. You should use your first init method.

If you have custom tableviewcell class then you dont need to implement initwithstyle initializer and also that initialilzer will not be called for primitive and class member objects.you will have to suffer.so follow the standard initialiser.

Basically your code and the code in the book do the same thing. there are different types of initializers for classes, you use the one you need. What the example in the book wants to show you is how to overwrite them if you need some custom initializers later on. Sow it's understandable that it doesn't really make sense from a real live application point of view but from a learning overwriting initializers and customization i think it's ok.

Related

Preventing subclasses overriding methods

Assume there is an object that initialises like so
- (void)doInit
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
[self doInit];
return self;
}
and it has a subclass which is inited in a similar way
- (void)doInit
{
NSLog (#"In SubClass init");
}
- (id)init
{
self = [super init];
[self doInit];
return self;
}
Now if I create an instance of child class then I receive the following output:
In SubClass init
In SubClass init
when really, what I meant to happen is
In BaseClass init
In SubClass init
Is there a way to mark doInit to say that it shouldn't be overridden or do I need to create a unique name for all methods in a subclass?
I'm not entirely sure how I haven't come across this issue before, but there you go.
Edit:
I understand why this is happening, I hadn't expected that the base class would be able to call the overridden function.
I also can't just call [super doInit]; from the Subclass method because the BaseClass still needs to call doInit so that creating an instance of BaseClass will still work. If I called [super doInit], I'd still end up getting SubClass's doInit called twice.
It appears the answer is no and I'll just have to uniquely name each doInit like doBaseClassInit and doSubClassInit.
If you have a method that you don't want to by dynamically bound (i.e. don't want a subclass method to be called if it exists), you need to do it as a C function instead. So, you could do this instead:
In the base class:
static void DoInit(BaseClass *self)
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
if (self) {
DoInit(self);
}
return self;
}
in the subclass:
static void DoInit(SubClass *self)
{
NSLog(#"In SubClass init");
}
- (id)init
{
self = [super init];
if (self) {
DoInit(self);
}
return self;
}
Note that both the DoInit methods are marked as static, so they are only visible each compilation unit (.m file) and don't conflict with each other.
You could, perhaps, try something like this in your base class. It would mean any time the init implementation inside BaseClass executed, the doInit implementation for BaseClass would be called.
- (void)doInit
{
NSLog(#"In BaseClass init");
}
- (id)init
{
self = [super init];
Class baseClass = [BaseClass class];
SEL selector = #selector(doInit);
IMP baseClassImplementation = class_getInstanceMethod(baseClass, selector);
baseClassImplementation(self, selector);
return self;
}
As I mentioned in my comment, if that's the narrowness of your need this should work as it gets around the dynamic method lookup involved with sending a message. Hope this helps!
EDIT:
Disclaimer - if you're in this situation it's probably not a good sign for the longevity of your design. This technique will get you up and running for now but please document it carefully, and consider ways to refactor your code so this is no longer used. Consider fixes like these to really be used only when extremely urgent.
The reason why you are not getting the "In BaseClass init" console message is because your subclass is not calling the super's implementation of doInit.
If you don't want doInit overridden the 'best' way to avoid doing so is to not publish the existence of this method. Remove it from your header and uniquely name the method so that a collision is unlikely. For example, many of the private methods in Apple's frameworks have a leading underscore. So, for example, you could call your method _doInit and it will be very unlikely that a subclass accidentally create it's own overiding implementation.
Nope, there's no enforceable way to prevent a subclass from overriding a method. The best you can do is to avoid putting it in the public header file for the class so someone is not likely to try to override it. If the method has to be public, you just put a note in the documentation for the method that it shouldn't be overridden or that subclasses should call super's implementation whichever the case may be. You'll find these kind of instructions all over in the documentation for Apple's own classes.
If you want your subclass to use the baseclass version of doInit then in the subclass don't declare a doInit method. Here's what I mean:
ClassA:
#interface ClassA :
-(void) doInit;
-(id) init;
#implementation
-(void) doInit {
NSLog(#"ClassA doInit");
}
-(id) init {
self = [super init];
if (self != NULL)
[self doInit];
return self;
}
ClassB
#interface ClassB : ClassA
-(id) init;
#implementation
-(id) init {
self = [super init];
if (self != NULL)
[self doInit];
return self;
}
And really you don't need to override the init method as well unless there's some special code that you want that class to do.

Why Default Constructor and Custom Constructor are called for subclasses of UINavigaionController?

#implementation NVController
//Plain Init method
-(id)init
{
self=[super init];
if(self)
{
}
return self;
}
//CustomInit Method
-(id)initWithRootViewController:(UIViewController *)rootViewController
{
self=[super initWithRootViewController:rootViewController];
if(self)`enter code here`
{
}
return self;
}
#end
NVController *instance=[[NVController alloc] initWithRootViewController:nil];
Here In above case ,Since I call only initWithRootViwController, another constructor init is also called. Any help would be appreciated.
This happens because you did not implement your initializers correctly.
In Objective C there is a concept of designated initializer, a single init function of your class that all other initializers must call. It is the designated initializer that calls [super init] directly; all other initializers need to call [super init] indirectly by invoking the designated initializer.
In your particular case you need to move the code common to both your init and initWithRootViewController:, if any, into the initWithRootViewController: initializer, and rewrite the plain init as follows:
-(id)init {
return [self initWithRootViewController:nil];
}
** EDIT :** (in response to the comment indicating that this solution causes an infinite recursion) I think the reason why you get infinite recursion has to do specifically with implementation details of UINavigationController, which should not be inherited. According to Apple's documentation,
The UINavigationController class implements a specialized view controller that manages the navigation of hierarchical content. This class is not intended for subclassing. Instead, you use instances of it as-is in situations where you want your application’s user interface to reflect the hierarchical nature of your content.
EDIT: The prohibition against subclassing has been lifted in iOS 6 - see the documentation for UINavigationController.
I guess initWithRootViewController: is implemented like this:
-(id)initWithRootViewController:(UIViewController *)rootViewController
{
self=[self init];
if(self)
{
// do something with rootViewController
}
return self;
}

Set args in -(id)init

is possible pass an args on an init method? I'm working with UITableViewController classes and UINavigatorController, when I push a new view whit:
[self.navigationController pushViewController:[[Class alloc] init] animated:YES];
I would also pass a string to that controller, is it possible?
It is not only possible, but it is normal and very common. In fact, it is quite common to have multiple initializers in classes where each initializer has a different combination of args, allowing you to initialize a class in different ways.
As you find yourself creating more than one initializer for a class, you should be sure to follow the best practices for making one initializer the "designated initializer". Here is a link to an article that demonstrates the principle of designated initializers.
Sure, just define your own initializer for your view controller subclass:
- (id)initWithStyle:(UITableViewStyle)style andSomeParameter:(NSString *)param
{
if (self = [super initWithStyle:style]) {
myInstanceVariable = [param retain];
}
return self;
}
(This is for a subclass of UITableViewController.)

Should subclasses call the designated initializer in the immediate super class?

I've seen some sample code which has got me wondering about calling the designated initializer in the super classes. Say I have some code this:
#interface NewTableViewCell : UITableViewCell {
}
#end
#implementation NewTableViewCell
- (id) initWithFrame: (CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Do some stuff
}
return self;
}
#end
Note that initWithFrame is the designated initializer for UIView, not UITableView. Should this code always be calling [UITableViewCell initWithStyle:reuseIdentifier:], or does it depend on the coder's intent?
When subclassing, the guideline is that the designated initializer has to call its super class' designated initializer.
Another guideline is that the subclass needs to override the superclass' designated initializer to call the new designated initializer.
If UITableViewCell follows this guideline (and it does; I tested with the help of a category), it overrides its superclass' designated initializer (UIView's initWithFrame:) to call the new designated initializer (initWithStyle:reuseIdentifier:). Therefore, if you call initWithFrame: on UITableViewCell it will call initWithStyle:reuseIdentifier:, which in turn will call initWithFrame: on super (UIView).
Therefore, it will need an additional method call but it will eventually go through initWithStyle:reuseIdentifier:.
Again, the best practice is that the designated initializer has to call the super class' designated initializer and any other initializer that isn't the designated initializer has to call the designated initializer. From "The Designated Initializer":
General principle: The designated initializer in a class must, through a message to super, invoke the designated initializer in a superclass.
Designated initializers are chained to each other through messages to super, while other initialization methods are chained to designated initializers through messages to self.
I agree it depends on the coders attempt but the coder should always try and use the designated initializer. Think about initializers you may have written, they probably do additional work for your object to be in a usable or desired state. If you are overriding an initializer like you are doing in your example you should call the overridden initializer as well. If that was a custom init method then you would want to call the designated initializer because for UITableViewCell's that is the only way to set the reuseIdentifier publicly.
//Override initWithFrame
//Fine although it may not (should not) get called for a UITableViewCell
- (id) initWithFrame: (CGRect)frame {
self = [super initWithFrame:frame];
//Design a custom initializer to gather parameters for supers default initializer
-(id)initWithCustomObject:(id)object style:(UI..Style)style reuseIdentifier:(NSString*)rid {
//This should call initWithStyle:reuseIdentifier:

Objective C - How do I inherit from another class?

- (id) init
{
[super init];
//initialitation of the class
return self;
}
I know that when I am inheriting from another class I am suppose to call super.init
Does this apply to "inheriting from NSObject" ?
Yes, usually you have something like:
- (id) init
{
if (self = [super init]) {
// instantiation code
}
return self;
}
Technically, yes, because Apple's documentation says init... methods should always include a call to super. However at present, the NSObject implementation of -init does nothing, so omitting the call wouldn't prevent your code from working.
The downside of omitting the call to super is that your code wouldn't be as robust to future changes; for example if you later changed the inheritance, or if (God forbid) Apple changed NSObject's -init method so that it actually did something essential.