Why can't a subclass mark a NS_DESIGNATED_INITIALIZER and use a convenience initializer from its superclass?

Why is it not valid (well I guess it's not "invalid" because it generates a warning not an error...) to call a convenience initializer on super from within the designated initializer of a subclass? Super's convenience initializer should be able to do normal set up. Why would I want to do this instead of just calling the designated initializer on super? Take this example.


-I have a NSWindowController subclass (with a nib). DisplayImagesWindowController, let's say.

-The NSWindowController subclass needs images to display. So our designated iniitializer will be:

-(instancetype)initWithImages:(NSArray<NSImages*>*)images NS_DESIGNATED_INITIALIZER;


Now, since I'm using a nib...and it is required to use this nib for this class, the implementation of initWithImages: is like this:


-(instancetype)initWithImages:(NSArray<NSImages*>*)images
{
   self =  [super initWithWindowNibName:@"DisplayImagesWindowController"]; //<--Call a convenience initializer. 
  if (self)
   {
       _images = images;
   }
   return self;
}


Far as I know, there is nothing wrong or invalid about how this window controller will be set up...but the compiler warns to use one of the designated initializers instead. It seems a little crazy for me to use initWithWindow: and do the nib loading myself?

The rule is that all convenience initializers on a class must funnel to one of its designated initializers via calls on self and the designated initializer must call one of the superclass's designated initializers via a call on super.


The potential problem that this rule avoids is the superclass accidentally calling a subclass convenience initializer and either initializing incorrectly or causing infinite recursion.


Consider a hypothetical future subclass of DisplayImagesWindowController that overrides -initWithWindow: to call through to your -initWithImages: method. Your -initWithImages: method calls through to super's -initWithWindowNibName:. But since -initWithWindowNibName: is not a designated initializer in the superclass, it should call a designated initializer. So, it will probably do [self initWithWindow:…] at some point. This won't go to NSWindowController's implementation, though, it will go to the hypothetical subclass's, and that sequence will loop until it blows the stack.


If your class only calls the designated initializer via super and the superclass's designated initializer only calls its superclass's designated initializer via super, etc., then there's no chance of anything accidentally calling out to a subclass's convenience initializer via self. Basically, designated initializers never call other initializers via self, only via super, which limits the search for the implementation as necessary.


Unfortunately, NSWindowController's -initWithWindowNibName: is not a designated initializer even though it is absolutely commonplace for people to write subclasses that call it the way you want to. Furthermore, such subclasses are typically informally "final" — that is, not designed to be subclassed — since they are intimately tied to the specifics of a particular NIB. Such final classes aren't really subject to the problem that this designated initializer rule is meant to solve, so breaking the rule is basically OK. If Apple were ever to change the inner workings of NSWindowController to cause a real problem with this pattern, it would break tons of code, so they probably won't. So, I'd continue to use it and use appropriate pragma directives to quiet the warning.


But, in the general case, you should follow the rule and heed the warnings.

Ken Thomases wrote:

Consider a hypothetical future subclass of DisplayImagesWindowController that overrides -initWithWindow:


---

I suppose I could sort of understand that argument in principle but have never ran into a situation where this set up was a problem. But even with this rule, which is fine with me (though I think it would be better if it wasn't so strictly enforced), I still kind of feel like I should be able to get the compiler to back off without suppressing the warning with macros if I mark initWithImages: as the designated initializer and I mark initWithWindow as NS_UNAVAILABLE...a portential subclass-er ought to be informed when it tries to call super on initWithWindow: that they are doing something wrong.

The hypothetical subclass is not calling -initWithWindow:, it's implementing its own method of that name. It's calling your -initWithImages:.

Hmm..that would be an odd choice for the programmer to make, I think. If the subclasser implemented initWithWindow: when initWithWindow is marked NS_UNAVAILABLE by the superclass...I'm not sure whether or not the subclass would be warned (never tried it)...


I haven't really thought about it through, but if you mark the designated initializer of your superclass unavailable...and then in your subclass marked a different designated initializer, then a subclass of you (so now we are at the grandchild of the window controller) should use its daddy's designated initializer, or one of its conveniance initializers in its designated initializer, so long as they aren't marked unavailable.


So this way, a subclass can inherit from its superclass by using a conveniance initializer, because a conveniance initializer should be able to set up a valid object for the superclass, but also take advantage of the conveniance of the initializer without having to reimplement stuff like nib loading. If there was a need to subclass ImageWindowController a subclass should just use a different nib to provide an alternative UI. Though this would sort of enforce all subclasses down the line to use Interface Builder, I suppose in order to set up a window.


Marking an initializer as the designated initializer and then using a nib is already locking that window controller into Interface Builder anyway....the reason why I'd do this (mark it designated) is to document it. Unless you want to go through the trouble of programmatically creating the interface for the window for every single window controller subclass you make, which nobody is going to do because IB is here for a reason.


I can see this being a real problem if your superclass' superclass' superclass' superclass (great grandfather) was written by soemone else and you didn't have access to the source code, and you were programming on a deserted island with no way to communicate with anyone else....and the header file wasn't commented...maybe the compiler warning would be helpful?

Read the Swift programming guide. Because Swift doesn't stop at issuing a warning for doing that, and the guide spells out their reasons for making it an error.

Did the programming guide mention anything about programming on a desert island, with no access to the internet, and using a class hierarchy four-five levels deep from libraries where you don't have access to the source code, and that the header files weren't commented? Joking.


But jokes aside. I'm still skeptical of this type of enforcement, it can sort of hinder advantages OO gives us (inheritance mainly). Forget ImagesWindowController. Let's say I wanted to make a new base window controller class to change the rules a bit. Call it InterfaceBuilderLoadingWindowController. InterfaceBuilderWindowController will do nothing more than change the designated initializer.


@interface InterfaceBuilderLoadingWindowController : NSWindowController
-(instancetype)initWithWindowNibName:(NSString *)windowNibName NS_DESIGNATED_INITIALIZER;
-(instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
-(instancetype)initWithWindow:(NSWindow *)window NS_UNAVAILABLE;
@end


All potential classes that inherit from InterfaceBuilderLoadingWindowController now ought to know that the designated initializer of this is initWithWindowNibName. But the compiler will tell you in InterfaceBuilderLoadingWindowController that initWithWindowNibName must call one of its superclass' designated initializer. And we have to manually reimplement nib loading. Any subclass of InterfaceBuilderLoadingWindowController that doesn't want to use a nib, shouldn't subclass InterfaceBuilderLoadingWinowController for obvious reasons.


While the compiler is great, you have to get yourself into a pretty big mess I think to get into this sort of trouble. Though of course, I'd love to mark designated initializers formally and mark invalid initializers NS_UNVAILABLE to document to those who want to create these objects which initializers they ought to use.

If you're not going to read the Apple documentation then you apparently aren't interesting in actually discussing the matter. I'm not interested in repeating the discussion in that material.


But if you're working on a desert island with no other software developers, then you can edit the header files and no one else will know. So who cares about the pesky compiler rules in that situation?

Of course I'm interested in discussing the matter. Why else would I make a thread about it? I'd like to hear if other devs arguments for or against some of these rules..and even throw out some longshot ideas in regards to changing the rules slightly maybe.


Maybe I'm completely out of my mind and there is no reason for declaring an initializer like my initWithImages: method as the designated initializer...even though at the moment it's the only initializer in my project that can create a valid ImageWindowController object (and the regular init, which calls through to it). But you can't use initWithWindow: right now to create a ImageWindowController object. I have no plans to make using the nib optional right now (maybe in the future...but then I will change the header to reflect that). It wasn't long ago when the rule was you declared one designated initializer informally by just commenting the header, and you could only have one. I do like being able to explicitly document it with a macro.


My previous post, of course was just a joke. Jeez. I of course can suppress the compiler warning. And if the answer to everything on the forums was "Read Apple's documentation" there would be no point to having forums. I will read the Swift docs on this, you gotta' give me some time though...I just made this thread yesterday and I'm trying to finish up this Mac app I've been working on. I haven't adopted Swift but I realize Swift has a lot to do with these changes/improvements to ObjC.

Another "rule" that seems like a bit of an annoyance is how I have to re-mark initWithCoder as a designated initializer for every view subclass I have that marks its own designated initializer (most of them do support being loaded from IB as well, but default properties values are configured in Interface Builder). Basically if you have a view subclass that has properties and all values are optional, you have to stick this in just to shut the compiler up:


-(instancetype)initWithCoder:(NSCoder *)coder
{
    return [super initWithCoder:coder];
}


Seems kind of silly.

Why can't a subclass mark a NS_DESIGNATED_INITIALIZER and use a convenience initializer from its superclass?
 
 
Q