Why do these NSView swift subclesses allow initialisation to run twice - swift

In the Swift subclasses below, when ChildView(dummy: NSObject()) is called, two different designated initializers for ParentView will be invoked, and the myLayer ivar of ParentView will be initialized twice.
Not that this will not happen in a Playground - that will correctly report that error: MyPlayground.playground:5:31: error: must call a designated initializer of the superclass 'NSView'
However, in an Xcode project (e.g. a Command Line Tool), this will compile and run fine.
I understand a lot of what's going on, and how to prevent this problem. But I'd love to understand why the compiler is not erroring, or at least warning me about this code (and preventing the double initialisation)
This code compiles cleanly under Swift 4.2, and in the debugger, I can see exactly what's happening:
When ChildView(dummy:) is called, it invokes ParentView(dummy:)
That's a designated initialiser, do the ParentView ivars are also initialised (myLayer)
Then ParentView(dummy:) calls super.init(), which is NSView.init()
The implementation of NSView.init() calls initWithFrame, so ChildView.init(frame:) gets invoked, and invokes ParentView.init(frame:). That's also a designated initialiser, so
myLayer gets initialised a second time.
To demonstrate this, given the classes below, invoke
let _ = ChildView(dummy: NSObject)
>>>> creating layer will get printed twice.
The way to avoid this would be to have ParentView(dummy:) call super.init(frame:), and then the problem goes away.
My question is really, how could this ever happen in the first place?
import Cocoa
class ParentView: NSView {
private var myLayer: CALayer = {
print(">>>> creating layer")
return CALayer()
}()
override init(frame frameRect: NSRect) { super.init(frame: frameRect) }
required init?(coder decoder: NSCoder) { super.init(coder: decoder) }
init(dummy: AnyObject?) { self.init() }
}
class ChildView: ParentView {
override init(frame frameRect: NSRect) { super.init(frame: frameRect) }
required init?(coder decoder: NSCoder) { super.init(coder: decoder) }
override init(dummy: AnyObject?) { super.init(dummy: dummy) }
}
This should generate a compile time error, as it does in a Playground.
I can't seem to construct a similar non-NSView class hierarchy that gives the same behaviour. No matter how I tweak the hierarchy, constructors, etc., Swift always gives compiler errors when I try to this with new objects (even when I mix Objective-C and Swift, to be analagous to NSView.
I can't find any public declaration for NSView.init() either, although I can see it executing in the debugger.

Related

NSWindowController designated initializer puzzle

I'm trying to make this code work:
class MyWindowController: NSWindowController
{
let thing: Thing
convenience init(thing: Thing)
{
self.thing = thing
super.init(windowNibName: NSNib.Name(rawValue: "MyNib"))
}
}
The problem, of course, is that a convenience initializer can't call init from a superclass. So how do I initialize my thing and still be able to call init(windowNibName:), which is itself a convenience initializer? I'd rather not have to re-implement the nib loading myself, but how do I avoid it if I can only use designated initializers?
According to the NSWindowController documentation:
You can also implement an NSWindowController subclass to avoid requiring client code to get the corresponding nib's filename and pass it to init(windowNibName:) or init(windowNibName:owner:) when instantiating the window controller. The best way to do this is to override windowNibName to return the nib's filename and instantiate the window controller by passing nil to init(window:). Using the init(window:) designated initializer simplifies compliance with Swift initializer requirements.
You can implement your class as:
class MyWindowController: NSWindowController
{
let thing: Thing
override var windowNibName: NSNib.Name? {
return NSNib.Name(rawValue: "MyNib")
}
init(thing: Thing) {
self.thing = thing
super.init(window: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Object is nil although it was set in init

I have an object called navigator, which I set within init. I break on it to make sure it is set. However when an IBAction func, linkButtonClicked, get's called and try's to use navigator I get a nil exception. Why?
class HomeCollectionViewCell: UICollectionViewCell {
let appDelegate:AppDelegate!
let navigator: Navigator!
#IBOutlet weak var linkButton: UIButton!
var destinationView:String?
var parentViewController:UIViewController?
#IBAction func linkButtonClicked(_ sender: Any) {
do {
try self.navigator.navigate(to: self.destinationView!, from: parentViewController!)
} catch {
}
}
required init?(coder aDecoder: NSCoder) {
self.appDelegate = UIApplication.shared.delegate as! AppDelegate
self.navigator = self.appDelegate.navigator
super.init(coder: aDecoder)
}
override func prepareForReuse() {
super.prepareForReuse()
// do resetting here if needed, like empty out data
linkButton.setTitle(nil, for: .normal)
}
}
The init?(coder: NSCoder) initializer gets used when you are retrieving the object from some kind of encoded store such as Core Data. This initializer is required by the NSCoding protocol and is used only for deserializing the object. Therefore, it does not get called at object creation. It only gets called if you serialize the object using NSCoding and later deserialize it.
The function you want to override in order to ensure some value will be set in your view is not its init (and if you really want to use its init, the method to overload is init(frame:)). Instead, you should set any variables you want to be available in the viewDidLoad method of the view controller.

Mandatory init override in Swift UINavigationController subclass

I'm currently subclassing a UINavigationController for a framework that serves a view controller flow (in a way, like UIImagePickerController do)
Here's an example of my implementation, reduced to be as simple as possible, that can be run in a playground.
import UIKit
public class MyNavigationController: UINavigationController {
public var anyVar: Int?
public init(anyVar: Int) {
let viewController = UIViewController()
super.init(rootViewController: viewController)
self.anyVar = anyVar
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
let navigationController = MyNavigationController(anyVar: 42)
The last line is crashing, with a EXC_BAD_INSTRUCTION. When I run in Xcode, it's tells me at runtime that init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) was missing.
And if I override the method:
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
...everything works well: you can try with your own playground.
I can't understand why. It doesn't sounds logic to me.
The UIViewController documentation says:
If you subclass UIViewController, you must call the super implementation of this
method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you,
and specify nil for both of this methods arguments.)
But my init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) override gets called, from super.init(rootViewController: viewController) initialization!
Without overriding it, I guess the UIViewController's init(nibName:bundle:) should be called, but not.
I still cannot understand why overriding the method and calling super make the program works better. IMO, overriding a method while only calling super.thisMethod is totally useless, it only adds a method call in the call stack.
I must missing something essentials about Swift init methods, but I can't figure out what.
This is happening because of the way how Swift inherits initializers. If you don't declare any of initializers in current class the compiler will inherit all of the initializers from the parent class. But if you override/add new initializers (and you do it with init(anyVar:)) Swift will not automatically inherit initializers from parent classes, so they are not accessible from subclass which is leading to runtime crash.
If you are interested in reasons beyond this behavior you can check out Intermediate Swift section and WWDC 2014 (somewhere around 34-minute mark they are talking about initializers inheritance)
You can assign root after you initiated super class
public class MyNavigationController: UINavigationController {
public var anyVar: Int?
public init(anyVar: Int) {
super.init(nibName: nil, bundle: nil)
viewControllers = [UIViewController()]
self.anyVar = anyVar
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

XCode: Swift strange behaviour and errors

I'm new to Swift, I'm trying to create a new Cocoa Application based on the following code:
Drag and Drop in Swift - Issues with Registering for Dragged Types?
I'm doing this:
create Swift Cocoa Application
add a Custom View
add a Swift Class subclass of NSView named DropView
set the Custom View class to DropView
paste the referenced code in DropView
Then I'm getting a few errors, and I'm not really understanding why and how to solve it. Can someone help me?
As the first error says, you need to add the override to your init method:
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
Also there are some changes in the draggingEntered and the draggingUpdated methods. You don't need the ! for your NSDraggingInfo anymore:
override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
return NSDragOperation.Copy
}
override func draggingUpdated(sender: NSDraggingInfo) -> NSDragOperation {
return NSDragOperation.Copy
}
To check the correct implementation of a method you want to override, just start typing the method-name in your file and Xcode will create you the method with header etc:
To fix your last error, you need to implement the required init the NSView needs:
required init?(coder: NSCoder) {
super.init(coder: coder)
}
To help you for further errors like these: Often Xcode itself helps you to solve the problems. Especially if you see the round error-circle on the left side of your editor. Just click on it and check what Xcode recommends:

Idiomatic pattern to initialize UIViews in Swift

In Objective-C I had evolved the pattern of having both an awakeFromNib and initWithFrame: method which invoked their super's and then called a _commonInit where I put all my own code. E.g.
- (void)_commonInit {
// Initialize stuff here
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _commonInit];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self _commonInit];
}
So I'm trying to reuse this pattern in my UIView subclasses that I'm porting to Swift:
func _commonInit() {
// initialize code here
}
override init(frame:CGRect) {
super.init(frame:frame)
self._commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self._commonInit()
}
Is this the right way to do it? I'm curious why the init(coder...) is required. Especially when all I do is call the super version. I seem to recall that the reason I used awakeFromNib in the Objc version was because any changes applied from nib restoration didn't happen until sometime later than initFromCoder:.
Having a commonInit method that gets called from your initializers is a perfectly fine pattern for cases where you have more than one designated (or required) initializer. It's not the only pattern, though.
To address each of your doubts in turn (and add some related points)...
init(coder:) is required because you're subclassing a class that declares conformance to the NSCoding protocol. That protocol demands that all instances of all subclasses be able to initialize from an archive.
You don't actually have to do anything in init(coder:) unless you save state in encodeWithCoder(_:), though. But because it's possible for a subclass to have encodable state that's not inherited, initialization safety requires that the subclass be responsible for this initializer (even if all it does is call super).
You use awakeFromNib() in any custom class loaded from a nib/storyboard to take care of initialization that needs to happen only after an object's outlets and actions have been hooked up.
When Cocoa (Touch) loads a nib, it first initializes each object (with init(coder:)), then after all the objects are "live", it connects all the IBOutlet variables and the targets for all of the IBActions sent by controls. After all that's done, it calls awakeFromNib().
There's a caveat—a paradox, even—to using the commonInit pattern in Swift: You must initialize properties (aka instance variables) before calling super.init(), and you can't access self (including to call methods on self) until after calling super.init(). So you can't have a commonInit method that sets the initial values of properties.
You can have properties whose types are implicitly unwrapped optionals. Those are automatically initialized to nil, and then you can set a "real" initial value in your commonInit method:
var name: String!
init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
init(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
private func commonInit() {
name = // something...
}
An alternative pattern that gets around this issue is to supply initializers (or even lazy initializers) for each of your properties. If you do this, you don't need a commonInit method, because the property initializers will be implicitly called from whichever init actually gets used (or in the case of lazy initializers, when the property is first accessed).
class MyView: UIView {
let fileManager = NSFileManager.defaultManager()
let appDelegate = UIApplication.sharedApplication().delegate as! MyAppDelegate
lazy var name: String = { /* compute here */ }()
init(frame: CGRect) { super.init(frame: frame) }
init(coder: NSCoder) { super.init(coder: coder) }
// ...
}
Finally, if you do provide a commonInit (or similar) method, you don't need to mangle the name with an initial underscore or anything—Swift has built-in access control, so any methods you don't want exposed to callers outside of a class can simply be marked private.