Why can't Swift initializers call convenience initializers on their superclass? - class

Consider the two classes:
class A {
var x: Int
init(x: Int) {
self.x = x
}
convenience init() {
self.init(x: 0)
}
}
class B: A {
init() {
super.init() // Error: Must call a designated initializer of the superclass 'A'
}
}
I don't see why this isn't allowed. Ultimately, each class's designated initializer is called with any values they need, so why do I need to repeat myself in B's init by specifying a default value for x again, when the convenience init in A will do just fine?

This is Rule 1 of the "Initializer Chaining" rules as specified in the Swift Programming Guide, which reads:
Rule 1: Designated initializers must call a designated initializer from their
immediate superclass.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html
Emphasis mine. Designated initializers cannot call convenience initializers.
There is a diagram that goes along with the rules to demonstrate what initializer "directions" are allowed:

Consider
class A
{
var a: Int
var b: Int
init (a: Int, b: Int) {
print("Entering A.init(a,b)")
self.a = a; self.b = b
}
convenience init(a: Int) {
print("Entering A.init(a)")
self.init(a: a, b: 0)
}
convenience init() {
print("Entering A.init()")
self.init(a:0)
}
}
class B : A
{
var c: Int
override init(a: Int, b: Int)
{
print("Entering B.init(a,b)")
self.c = 0; super.init(a: a, b: b)
}
}
var b = B()
Because all designated initializers of class A are overridden, class B will inherit all convenience initializers of A. So executing this will output
Entering A.init()
Entering A.init(a:)
Entering B.init(a:,b:)
Entering A.init(a:,b:)
Now, if the designated initializer B.init(a:b:) would be allowed to call the base class convenience initializer A.init(a:), this would result in a recursive call to B.init(a:,b:).

It's because you can end up with an infinite recursion. Consider:
class SuperClass {
init() {
}
convenience init(value: Int) {
// calls init() of the current class
// so init() for SubClass if the instance
// is a SubClass
self.init()
}
}
class SubClass : SuperClass {
override init() {
super.init(value: 10)
}
}
and look at:
let a = SubClass()
which will call SubClass.init() which will call SuperClass.init(value:) which will call SubClass.init().
The designated/convenience init rules are designed that a class initialisation will always be correct.

I found a work around for this. It's not super pretty, but it solves the problem of not knowing a superclass's values or wanting to set default values.
All you have to do is create an instance of the superclass, using the convenience init, right in the init of the subclass. Then you call the designated init of the super using the instance you just created.
class A {
var x: Int
init(x: Int) {
self.x = x
}
convenience init() {
self.init(x: 0)
}
}
class B: A {
init() {
// calls A's convenience init, gets instance of A with default x value
let intermediate = A()
super.init(x: intermediate.x)
}
}

Consider extracting the initialization code from your convenient init() to a new helper function foo(), call foo(...) to do the initialization in your sub-class.

Look at the WWDC-video "403 intermediate Swift" at 18:30 for an in depth explanation of initializers and their inheritance. As I understood it, consider the following:
class Dragon {
var legs: Int
var isFlying: Bool
init(legs: Int, isFlying: Bool) {
self.legs = legs
self.isFlying = isFlying
}
convenience initWyvern() {
self.init(legs: 2, isFlying: true)
}
}
But now consider a Wyrm-subclass:
A Wyrm is a Dragon with no legs and no wings. So the Initializer for a Wyvern (2 legs, 2 wings) is wrong for it! That error can be avoided if the convenience Wyvern-Initializer simply can't be called but only the full designated Initializer:
class Wyrm: Dragon {
init() {
super.init(legs: 0, isFlying: false)
}
}

Why don't you just have two initializers - one with a default value?
class A {
var x: Int
init(x: Int) {
self.x = x
}
init() {
self.x = 0
}
}
class B: A {
override init() {
super.init()
// Do something else
}
}
let s = B()
s.x // 0

Related

Swift: how to avoid rewriting this init() in all inheriting classes?

I have this class and protocol in a framework:
public protocol P {}
open class C {
public init(_ p: P.Type) {
//super.init() etc
}
}
And in the project using this framework:
enum E: P {
// cases...
}
The thing that bugs me is that for every class that inherits C, I need to define the same init() like this:
final class C1: C {
init() {
super.init(E.self)
}
}
final class C2: C {
init() {
super.init(E.self)
}
}
// etc...
Is there a way for me to declare this default init in my project, like using an extension this way:
extension C {
// Declare the init(E.self) here somehow?
}
This way, I would simply call C1(), C2() etc without defining it in the subclass.
Thanks for your help.
You could create a protocol that contains the init, extend the protocol and provide a default implementation, and assign the protocol to C.
public protocol P {}
enum E: P {
}
protocol A {
init(_ p: P.Type)
}
extension A {
init(_ p: P.Type) {
// Add a default implementation
self.init(E.self)
}
}
class C: A {
// If you want to override, note you need to add `required`
required init(_ p: P.Type) {
}
}
class C1: C {
// No need to init here
}
Or if you don't want another protocol, you will need a new class that implements the init and subclass C and have your C1 and C2 inherit this new class.
Its what is usually done when people create BaseUIViewController and make their UIViewControllers subclasses of this:
public protocol P {}
enum E: P {
}
class C {
init(_ p: P.Type) {
}
}
class CBase: C {
// Override and provide default implementation
override init(_ p: P.Type) {
super.init(E.self)
}
}
class C1: CBase {
// No need to init here
}
class C2: CBase {
// No need to init here
}
Declare a convenience initialiser
extension C
{
public convenience init()
{
self.init(E.self)
}
}
let c1 = C1()
let c2 = C2()
Or you can put the convenience initialiser in the main definition of C.

How can i pass class as a parameter to a function in Swift?

Let us consider i have two different classes.
class A {
var something = "Hello"
}
class B {
var something = "World"
}
Now
class C {
func request() {
//Call with class A or B it can contain any class. I can call either class A or B depending on condition
update(myClass: A or B)
}
func update(myClass:A or B ) {
print(myClass.something) //Since both class have same varaible var something so this code should work either i pass class A or B through function
}
}
Plz help me achieve this using Swift
You cannot declare a function in Swift that could accept an input argument of several different types, so you cannot declare a type as A or B. However, you don't actually need this to solve your specific problem.
Since you want to access a common property of the two class instances, you should declare that property in a protocol, make both classes conform to that protocol, then make the function take an input argument of the protocol type.
protocol SomethingProtocol {
var something: String { get }
}
class A: SomethingProtocol {
let something = "Hello"
}
class B: SomethingProtocol {
let something = "World"
}
class C {
func request() {
//Call with class A or B it can contain any class. I can call either class A or B depending on condition
update(something: A())
update(something: B())
}
func update(something: SomethingProtocol) {
print(something.something) //Since both class have same varaible var something so this code should work either i pass class A or B through function
}
}
Use a protocol
protocol MyProtocol: class {
var something: String { get set }
}
class A: MyProtocol {
var something = "Hello"
}
class B: MyProtocol {
var something = "world"
}
class C {
func update(myClass:MyProtocol ) {
print(myClass.something) //Since both class have same varaible var something so this code should work either i pass class A or B through function
}
}
usage:
let a = A()
let b = B()
let c = C()
print(c.update(myClass: a))
print(c.update(myClass: b))
Output:
hello
world
Create a protocol that both A and B conforms to and use it as the parameter type in update()
protocol SomeProtocol {
var something: String {get set}
}
func update(_ o: SomeProtocol) {
print(o.something)
}
Let it be known that I think using a protocol is the cleanest option that will best solve your problem.
However, it is possible to use Any to pass any object as a parameter, this will require checking which class you are dealing with inside your update method.
Something like this...
class C {
func update(myClass: Any) {
if let a = myClass as? A {
print(a.something)
}
if let b = myClass as? B {
print(b.something)
}
}
}
This might be neater as a switch - ref
class C {
func update(myClass: Any) {
switch myClass {
case let a as A:
print(a.something)
case let b as B:
print(b.something)
default:
print("not a thing")
}
}
}

Constant property cannot be initialized in a convenience initializer of a derived class

Below is sample code from a playground. What I don't understand is why the b variable in the subclass must be a var type and cannot be a let. Can someone help me understand?
class Base1 {
init() { }
}
class Sub1: Base1 {
let b: Int
override init() {
super.init()
}
convenience init(b: Int) {
self.b = b // Cannot assign to property: 'b' is a 'let' constant
self.init()
}
}
I think this has less to do with subclassing and more to do with the class initializers.
The error only tells half the story, changing b from let to var would show other problems with this class:
Initializers must set an initial value for each stored property of the class. If you override init, you must provide a default value for b.
A convenience initializer must call a designated initializer before accessing self
This is how it looks (thanks to Martin R for the improvements suggested in the comments on how to maintain b as a let constant):
class Base1 {
init() { }
}
class Sub1: Base1 {
let b: Int
convenience override init() {
self.init(b: 5)
}
init(b: Int) {
self.b = b
super.init()
}
}
let one = Sub1(b: 10)
one.b // prints 10
let two = Sub1()
two.b // prints 5
In Swift, designated initializers are used to set up all the stored properties. This is from the official documentation:
Designated initializers are the primary initializers for a class. A
designated initializer fully initializes all properties introduced by
that class and calls an appropriate superclass initializer to continue
the initialization process up the superclass chain.
Convenience initializer are secondary, and needs to call desginated initializer.
You can also define a convenience initializer to create an instance of
that class for a specific use case or input value type. Create convenience initializers whenever a shortcut to a common initialization pattern will save time or make initialization of the class clearer in intent.
When you are trying your code, although the error message is not clear at this point, the issue is that you cannot use convenience init to initialize a stored property. You have to make b optional var to make it work.
class Sub1: Base1 {
var b: Int?
override init() {
super.init()
}
convenience init(b: Int) {
self.init()
self.b = b
}
}
Or better way is, as #Martin R suggested in one of the comments, to create another designated initializer and use convenience before overridden init, to give a default value:
class Sub1: Base1 {
let b: Int
init(b: Int) {
self.b = b
super.init()
}
convenience override init() {
self.init(b:5)
}
}
First of all, imho, this has nothing with derived class, you could remove you subclassing and get the similar error.
Second, I think it is due to the "nature" of convenience initializers and their usage. They are "optional" (in usage) initializers, for example to provide shortcut way to initialize from some complex data (like other class or structure). Adding initialization of self.b to convenience init in your sample wouldn't bring anything helpful, as you anyway would need to initialize b in designated init:
class Sub1: Base1 {
let b: Int
// You must init b
// Or ger error: property 'self.b' not initialized ...
init(b: Int) {
self.b = b
super.init()
}
// Do you really need the code below now?
//convenience init(b: Int) {
// self.b = b
// self.init()
//}
}
Thus as designated init must initialize b, the convenience initializer as your wrote it becomes meaningless.
Third, assume it would be allowed. So, as convenience initializer are delegated across not necessarily calling the designated one. Thus it would be possible to do something like that:
...
convenience init(b: Int) {
self.b = b
self.init(c: 10)
}
// later some crazy guy adds his own "convenience" initializer and points your to this one.
convenience init(c: Int) {
self.c = c
if c == 7 {
self.b = 11
}
self.init()
}
Now imagine that you are the "compiler" and tell me where the constant b is set to it's constant value and what value?
Finally, in my understanding the correct usage could be like that:
class Base1 {
init() {}
}
class Sub1: Base1 {
let b: Int
init(b: Int) {
self.b = b
super.init()
}
// Convenience initializer providing default value
override convenience init() {
self.init(b: 7)
}
}
So, my concern is that I don't understand clearly what you really wanted to achieve by allowing let b initialized in convenience init?

Any way to get the calling class on static functions in Swift?

The question is better explained in code:
class A {
class func thefunc() -> String {
/* Can I here know if thefunc was called using
A.thefunc() or
B.thefunc()?
*/
return "A" /* or "B"= */
}
}
class B: A {
}
You can use self in a static method to refer to the type (as compared to the instance for using self in an instance method)
class A {
class func thefunc() -> A.Type {
return self
}
}
class B: A { }
let metaTypeA = A.thefunc() // A.Type
let metaTypeB = B.thefunc() // B.Type
Similarly, you can use runtime introspection, specifically the subjectType property of the Mirror representation of self.
Instance Variables
...
var subjectType: Any.Type
The static type of the subject being reflected.
From the swiftdoc.org reference of Mirror structure.
E.g.:
class A {
class func thefunc() {
print(Mirror(reflecting: self).subjectType)
}
}
class B: A { }
A.thefunc() // A.Type
B.thefunc() // B.Type
Alternatively, if you needn't actually make use of the meta-type (just differ between the "static caller"), you could use the String representation of self.
class A {
class func thefunc() -> String {
return String(self)
}
}
class B: A { }
print(A.thefunc()) // A
print(B.thefunc()) // B

class function with generic return value

Right now I have the following classes:
class A {
class func instantiate() -> A {
return MakeObject()
}
}
class B: A {}
let x = B.instantiate()
This results in x being of type A. How can I change instantiate in order to return an instance of the subclass that was called from? In other words, so that x ends up being of type B.
EDIT:
This is what I used to solve it, based on Martin R's answers:
class A {
class func instantiate() -> Self {
func helper<T>() -> T {
return MakeObject() as! T
}
return helper()
}
}
The returns type needs to be Self (which is the concrete type when
the class method is called), and initialization must be done with a
required init method (which can be overridden in a subclass):
class A {
class func instantiate() -> Self {
return self.init()
}
required init() {
}
}
class B: A {}
let x = B.instantiate() // `x` has type `B`
Alternatively, just define an init method
init(parameters ...) {
}
which "automatically" returns instances of the class that is is
called on.