Adding Convenience Initializers in Swift Subclass

Adding Convenience Initializers in Swift Subclass

My understanding of Initializer Inheritance is the same as yours, and I think we are both well aligned with what the book states. I don't think it's an interpretation issue or a misunderstanding of the stated rules. That said, I don't think you're doing anything wrong.

I tested the following in a Playground and it works as expected:

class RectShape: NSObject {
var size = CGSize(width: 0, height: 0)
convenience init(rectOfSize size: CGSize) {
self.init()
self.size = size
}
}

class SquareShape: RectShape {
convenience init(squareOfSize size: CGFloat) {
self.init(rectOfSize: CGSize(width: size, height: size))
}
}

RectShape inherits from NSObject and doesn't define any designated initializers. Thus, as per Rule 1, it inherits all of NSObject's designated initializers. The convenience initializer I provided in the implementation correctly delegates to a designated initializer, prior to doing the setup for the intance.

SquareShape inherits from RectShape, doesn't provide a designated initializer and, again, as per Rule 1, inherits all of SquareShape's designated initializers. As per Rule 2, it also inherits the convenience initializer defined in RectShape. Finally, the convenience initializer defined in SquareShape properly delegates across to the inherited convenience initializer, which in turn delegates to the inherited designated initializer.

So, given the fact you're doing nothing wrong and that my example works as expected, I am extrapolating the following hypothesis:

Since SKShapeNode is written in Objective-C, the rule which states that "every convenience initializer must call another initializer from the same class" is not enforced by the language. So, maybe the convenience initializer for SKShapeNode doesn't actually call a designated initializer. Hence, even though the subclass MyShapeNode inherits the convenience initializers as expected, they don't properly delegate to the inherited designated initializer.

But, again, it's only a hypothesis. All I can confirm is that the mechanics works as expected on the two classes I created myself.

Designation & Convenience Initializer

Since you have two designated initializers in A, these are non-ambiguous due to different signatures, and can be differentiated from each other. This means you can readily call the appropriate designated initializer of A from the subclass B. E.g., for A as

class A {
var a: Int

// designated "#1"
init() {
a = 0
}

// designated "#2"
init(_ a: Int) {
self.a = a
}

// convenience initializer for A
convenience init(_ foo: String) {
print(foo)
self.init(42)
}
}

We have, e.g.

class B: A {
let b: Int

// designated "sub#1"
override init() {
b = 42
super.init() // use A's designated #1
}

// designated "sub#2"
override init(_ a: Int) {
b = 24
super.init(a) // use A's designated #2
}

// non-overriding designated "#3"
init(b: Int) {
self.b = b
super.init() // use A's designated #1
}

// convenience initializer for B
convenience init(_ foo: String) {
print(foo)
self.init() // use B's designated #1
}
// you may only call designated initializers of the superclass
}

Note however that the initializers of a subclass may only call designated initializers of its superclass. This is covered by rules 1 and 2 in the Language Guide - Initializers - Initializer Delegation for Class Types [emphasis mine]:

Initializer Delegation for Class Types


To simplify the relationships between designated and convenience
initializers, Swift applies the following three rules for delegation
calls between initializers:

Rule 1

A designated initializer must call a designated initializer from its
immediate superclass
.

Rule 2

A convenience initializer must call another initializer from the same
class
.

Rule 3

A convenience initializer must ultimately call a designated
initializer.

So you may not access the convenience initializers of A from the convenience initializers of B, and the designated initializers of B must naturally, by rule 1 above, call only the designated initializers of A.

Swift - Convenience initializer in subclass of CIDetector is not working

Swift imports some class methods as convenience initializers (Class Factory Methods and Convenience Initializers).

In your case, init(ofType:context:options:) is a class method of CIDetector in Objective-C + detectorOfType:context:options:.

Such convenience initializers are not available in subclasses. It's a natural conclusion based on the fact that such factory method of a class always creates an instance of the class, it cannot create an instance of the subclass you defined.

So, if you want to provide another convenience initializer utilizing a convenience initializer based on Class Factory Method, you may need an extension.

extension CIDetector {

convenience init?(rectDetectorWithAspectRatio aspectRatio: Float) {
let options: [String : Any] = [CIDetectorAccuracy : CIDetectorAccuracyHigh,
CIDetectorAspectRatio : NSNumber(value: aspectRatio)]
self.init(ofType: CIDetectorTypeRectangle, context: nil, options: options)
}

}

By the way, the diagnostics messages are seemingly completely broken from the programmers' side using Swift. You can send a Bug Report about it.

Create convenience Initializer on subclass that calls failable Initializer

The initialiser delegation rule #2 states

A convenience initializer must call another initializer from the same
class.

Your class doesn't define init?(named:String), so it will call superclass initialiser (which you do have access to under the other rule #2 you were referring to), but this won't satisfy the requirement to call a non-convenience initialiser from your class.

You can simply call self.init before calling the superclass initialiser.

convenience init? (i:UIViewController){
self.init()
self.init(named:"")
}


Related Topics



Leave a reply



Submit