Question about subclassing NSWindowController

I think the Apple documentation could use a lot of improvement and definitely in situ examples for everything as the .NET documentation pretty much does, but this isn’t a rant about that. However, better documentation may eliminate the need to ask this.

I admit I still don’t know all the rules for initializers by heart, so I wanted to try something simple - to create an initializer in MainWindowController, which is subclassed from NSWindowController in many of the book’s examples.

After a few guesses, I took to Google to see how to do this without compiler errors and I cannot seem to find anything. Whether the XCode, etc., changed and made this even more restrictive or what, I don’t know. Here’s the code (at least one iteration):

import Cocoa

class MainWindowController: NSWindowController {
    
    var newVar: String
    
    init() {
        self.newVar = "newVar"
        super.init()
        fatalError("init() has not been implemented")
    }
    
    override init(window: NSWindow?) {
        super.init(window: window)
        fatalError("init(window:) has not been implemented")
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        fatalError("init(coder:) has not been implemented")
    }
    
    override func windowDidLoad() {
        super.windowDidLoad()
    }
    
    override var windowNibName: String? {
        return "MainWindowController"
    }
}

The super.init() line in the first initializer generates the following errors / comments:

[quote]Must call a designated initializer of the superclass 'NSWindowController’
Convenience initializer is declared here[/quote]

Either I’m missing something obvious or this cannot be done since I have yet to find sample code online that actually works.

Any ideas?

Your problem is a thorny one.

  1. If you create a designated initializer in MainWindowController, as you have done by defining init(), then inside the designated initializer you have to call NSWindowController’s designated initializer:

But NSWindowController’s designated initializer requires that you call it with an argument that is an NSWindow–and you would like to avoid having to construct an NSWindow by hand with code; instead you want to use the window in the .xib file.

  1. What you would like to do to create the window is call NSWindowController’s convenience initializer, which takes a String argument that is the name of the .xib file:

But as noted above, MainWindowController’s designated initializer cannot call a convenience initializer in the super class, and likewise you can’t define a convenience initializer in MainWindowController that calls a convenience initializer in the super class because a convenience initializer has to call another initializer in the same class.

So it appears that convenience initializers aren’t so convenient after all because you can’t call them from a subclass’s initializers.

[quote]The super.init() line in the first initializer generates the following errors / comments:

[quote]Must call a designated initializer of the superclass 'NSWindowController’
Convenience initializer is declared here[/quote][/quote]

If you check the NSWindowController docs, the designated initializer is:

And inside MainWindowController’s designated initializer:

init() { self.newVar = "newVar" super.init() fatalError("init() has not been implemented") }

…you never called:

Solutions for how to to subclass NSWindowController are described here:

stackoverflow.com/questions/2422 … downibname

The following works for me in Xcode 6.3.2:

[code]class MainWindowController: NSWindowController {

...
...

/*
//Causes errors:
override init()
{
newVar = "Hello"
super.init()
}
*/
override init(window: NSWindow?)
{
newVar = "World"
super.init(window: window)
}

required init?(coder: NSCoder)
{
    newVar = "Goodbye"
    super.init(coder: coder)
}

}[/code]

Apparently, if you override all the super class’s designated initializers, then your class will inherit all the superclass’s convenience initializers, so you can create an instance of MainWindowController like this:

That uses NSWindowController’s convenience initializer which takes the .xib file name as an argument. That code ends up setting newVar to “World” because NSWindowController’s convenience init calls NSWindowController’s designated initializer, which takes an NSWindow as an argument, and that designated initializer has been overridden in MainWindowController, so the version in MainWindowController is called. Note that NSWindowController’s convenience initializer takes the .xib file name passed as an argument and creates an NSWindow somehow, then calls NSWindowController’s designated initializer using the newly created NSWindow as the argument.

I would guess that all the trouble you are having is the reason the book never had us create our own initializer in MainWindowController.

Because NSWindowController adopts the NSCoding protocol, NSWindowController apparently inherits a designated initializer from the protocol. I have no idea what the question mark means after “init”, and I had no luck finding any mention of that syntax. Well, if I get rid of the ? after “init”, Xcode tells me that the call to super.init(coder: coder) is “fallible”, and the suggested fix is to add the ? after “init”. I guess that indicates that the method:

might not return a valid instance. I wonder why NSCoding doesn’t specify its init() with a question mark in the docs to indicate that it is fallible?

Here is a good explanation of fallible initializers:

developer.apple.com/swift/blog/?id=17

Thanks for the detailed response. I guess you can see why I finally had to post something to see what I was missing. I had already looked at that StackOverflow link and tried some of the recommendations, none of which worked (that is, they all had at least one compiler error).

My complaint about the Apple documentation is you have to “chase” the hierarchy up the chain to find all the designated initializers and then trying to satisfy the requirements with limited explanation as to how/why. Looking at the headers for the classes helps to some degree, but at least to me, it could be done so much better. As I mentioned in my original post, examples / sample code goes a long way toward making abstract concepts more understandable.

Thanks again!

Hey, your’e welcome.

With the same designated initializer overrides, I was also able to create a MainWindowController instance like this:

The newVar property gets set to “World” as well. I can’t grok how that works. That code looks like it calls MainWindowController’s default initializer–but you don’t get a default initializer if you declare a property without a default value, like newVar. NSWindowController doesn’t have a no-arg initializer, so up the inheritance chain we go to NSObject, which does have a no arg initializer…but how does that end up causing the following initializer to execute:

override init(window: NSWindow?) { newVar = "World" super.init(window: window) }

:astonished: :open_mouth: :confused:

THAT is definitely confusing … maybe it’s because you only need to satisfy the “no arg” initializer by including it, but that’s all that’s needed (that is, it needs to be a presence and nothing more??). In a way, it makes sense that the actual initializer that does the work is the designated initializer actually called out for the NSWindowController class and not those initializers of its superclass.

Apple would do the developer community a favor by being more detailed in the documentation, but then again, maybe they want to discourage subclassing some of the more complex / arcane classes for [fill in reason here].

Your info is great, man! Thanks. :slight_smile:

I came to the conclusion that there had to be a no arg convenience init() in NSWindowController which is implemented to call the designated initializer. I asked a question about that on SO, and one person posted that in Objective-C if you call:

the init() method in NSWindowController is implemented to call:

…which in swift would be equivalent to calling:

And that is a call to the designated initializer in NSWindowController, which we overrode to set the newVar property. The no arg NSWindowController convenience init() is totally undocumented and doesn’t appear in NSWindowController.h either.

Crazy! But there had to be a reason it didn’t work as expected with the existing documentation. Good sleuthing!

Full circle to your point about bad docs. :smiley:

Our whole book is based on calling that undocumented no-arg convenience init() in NSWindowController when we write: