Designation & Convenience Initializer - ios

I have a query regarding initializer in swift. As per Apples' Swift3.0
A designated initializer must call a designated initializer from its immediate superclass.
A convenience initializer must call another initializer from the same class.
A convenience initializer must ultimately call a designated initializer.
Now I have two class A & B (subclass of A). A & B both have two designated initializer with below structure
Can anyone suggest how class B's designated initializer will call to class A's initializers?

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.

Related

How to delegate up to a superclass initializer before assigning a value to an inherited property

Swift documentation says here in regards to the initializer delegation:
A designated initializer must delegate up to a superclass initializer
before assigning a value to an inherited property. If it doesn’t, the
new value the designated initializer assigns will be overwritten by
the superclass as part of its own initialization.
Let's say in the following example:
class Shape {
var color: String
init(color: String) {
self.color = color
}
}
class Circle: Shape {
let pi = 3.14
var r: Double
init(r: Double, color: String) {
self.r = r
super.init(color: color)
}
func area() -> Double {
return pi * r * r
}
}
what does it mean to "delegate up to a superclass initializer before assigning a value to an inherited property"? My assumption is that the "inherited property" means none of the properties newly introduced by the subclass.
Isn't the very act of delegating up to a superclass initializer, super.init(color: color), assigning a value to inherited property? How do I assign to the inherited properties first and then delegate up to a superclass initializer? What would be an example of the new value being overwritten by the superclass initializer?
Your example doesn't actually involve the modifying an inherited property in a subclass initialiser. An inherited property is a property declared by the superclass (such as color in your example).
That piece of documentation says that if you want to override the behaviour of the super initialiser and modify the value of an inherited property in the subclass init, you first need to call super.init and only then can you modify the value. This is because all values declared by the superclass will be initialised in super.init.
Actually, the compiler doesn't even allow you to access values declared in the superclass before calling super.init to avoid the errors mentioned by the documentation.
Check the below example, where the superclass assigns a constant value to id, regardless of what the input value to the init was, while the subclass uses the input value and hence overrides the value assigned in the superclass.
class Super {
var id: Int
init(id: Int) {
self.id = 0
}
}
class Sub: Super {
let prop: String
init(id: Int, prop: String) {
self.prop = prop
// The next line would actually result in the error: 'self' used in property access 'id' before 'super.init' call
//self.id = id
super.init(id: id)
self.id = id // this is where you can override a property declared by the superclass
}
}
Sub(id: 2, prop: "") // id is correctly initialised to 2

UNNotificationCategory subclass init issue

I want to subclass UNNotificationCategory(UserNotifications), because I want to use enums instead of hard coded strings as category identifiers.
There is one convenience init inside UNNotificationCategory definition
public convenience init(identifier: String, actions: [UNNotificationAction], intentIdentifiers: [String], options: UNNotificationCategoryOptions = [])
I am not able to write an initializer for my subclass.
I understand I cant have designated initializer inside the subclass because I want to call the convenience init of superclass. But my convenience init is also throwing complier error.
Here's the code:
enum PushNotificationCategoryIdentifier:String {
}
convenience init(categoryIdentifier:PushNotificationCategoryIdentifier, actions:[UNNotificationAction], intentIdentifiers:[String], options: UNNotificationCategoryOptions) {
self.init(identifier: categoryIdentifier.rawValue, actions: actions, intentIdentifiers: intentIdentifiers, options: options)
}
This is resulting in error: self.init isn't called on all paths before returning from initializer
I guess this is because this class is implemented in Objective-C and may be they have not called the designated initailizer from convenience initailizer(as Objective-C classes dont have to call designated initializer from convenience initailizer).
But does that mean I can't subclass UNNotificationCategory if I want to write an initializer in it?
No you can do this. You will have to define init() method for this. Right now you have only defined convenience init(). But you will have to define init()in your subclass.
When you write a convenience init() it is only there to help initialization in an easy way but still you will have to call designated init with syntax init() from the convenience init().
You can read it on Apple Official Documentation

Why is the superclass designated initializer getting called by default? [duplicate]

This question already has an answer here:
Why doesn't Swift force my designated initializer to call super?
(1 answer)
Closed 6 years ago.
I am new to Swift and getting some problem regarding initializers. I have created a Swift file with the following code :
import Foundation
class SuperClass
{
var a : Int
init()
{
a = 10
print("In Superclass")
}
}
class SubClass : SuperClass
{
override init()
{
print("In Subclass")
}
}
In the above code, init() of SubClass does not contain call to init() of SuperClass i.e. there is no super.init() in SubClass init().
So my question is:
1. Why is it not giving any error if I don't call designated init() of SuperClass
2. If I am creating an object of SubClass i.e. let s = SubClass(), the output is :
In Subclass
In Superclass
Why the init() of SuperClass is getting called? Does a subclass init() calls the superclass init() by default?
As far as I understood your question, you're not only wondering why, when and how the initializer gets called automatically, but also complaining about the missing documentation of this behavior.
First of all I agree with you on the lack of documentation - just like you I'm not able to find anything about this behavior and therefore it should be added to the documentation by Apple.
Why super.init() is called:
As per documentation a designated initializer of the superclass has to be called by a designated initializer of its subclass in order to fully initialize all properties.
Rule 1
A designated initializer must call a designated initializer from its
immediate superclass.
Your code example above proves it's obviously done implicitly: print("In Superclass") prints to the console, so super.init() is somehow invoked while creating an instance.
When and how super.init() is called:
There are some conditions to be met in order to allow the compiler to call the designated initializer of the superclass implicitly:
The superclass must have only one designated initializer which
is then called. Otherwise the compiler had to choose one to delegate
to. This single designated initializer could be also the default
initializer or an inherited initializer.
class SuperClass {
var a: Int
init() {
a = 10
}
// introduction of a second designated initializer in superclass:
init(withValue value: Int) {
a = value
}
}
class SubClass: SuperClass {
// won't compile:
// "error: super.init isn't called on all paths before returning from initializer"
override init() {}
}
The single designated initializer of the superclass mustn't have any
parameters. After all the compiler wouldn't know any appropriate
parameter to be passed.
class SuperClass {
var a: Int
// declaration of an initializer with parameter:
init(withValue value: Int) {
a = value
}
}
class SubClass: SuperClass {
// won't compile:
// "error: super.init isn't called on all paths before returning from initializer"
override init() {}
}
The designated initializer of the subclass mustn't further read or
modify (inherited) instance properties of the superclass or call
instance methods of the superclass. That's because of Swift's
two-phase initialization process with its corresponding safety
checks and the fact that the implicit delegation up to the designated
initializer of the superclass happens at the end of the
init-Statement in the subclass.
Safety check 2
A designated initializer must delegate up to a
superclass initializer before assigning a value to an inherited
property. If it doesn’t, the new value the designated initializer
assigns will be overwritten by the superclass as part of its own
initialization.“
Safety check 4
An initializer cannot call any instance methods, read
the values of any instance properties, or refer to self as a value
until after the first phase of initialization is complete.
class SuperClass {
var a: Int
init() {
a = 10
}
}
class SubClass: SuperClass {
// won't compile:
// "error: use of 'self' in property access 'a' before super.init initializes self"
override init() {
a = 10 // modifying inherited self.a before phase 1 of initialization completes isn't valid!
// implicit delegation to super.init()
}
}
Safety check 1
A designated initializer must ensure that all of the
properties introduced by its class are initialized before it
delegates up to a superclass initializer.
class SuperClass {
var a: Int
init() {
a = 10
}
}
class SubClass: SuperClass {
// introduction of instance property "b"
var b: Int
// compiles finely:
override init() {
b = 10 // initializing self.b is required before delegation!
// implicit delegation to super.init()
}
}
I hope that helps.
Why the init() of SuperClass is getting called? Does a subclass init() calls the superclass init() by default?
Basically, yes.
If all the rules say that you should say super.init() and you don't say it, it is called for you.
I don't like this behavior; it is poorly documented, and besides, secretly doing stuff for you seems against the spirit of Swift. But I filed a bug against it long ago and was told it was intended behavior.
Every class have at least one designated initializer which is responsible for initializing instance variables.
Here is an extract from the doc :
Classes tend to have very few designated initializers, and it is quite common for a class to have only one. Designated initializers are “funnel” points through which initialization takes place, and through which the initialization process continues up the superclass chain.
Every class must have at least one designated initializer. In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass, as described in Automatic Initializer Inheritance below.
You can refer to the complete documentation for further details : https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html
A rule of thumb
Create one designated initializer for your class.
Call the designated initializer of the superclass or let the system figure this out for you.
Create zero or more convenience initializers which will call your designated initializer.
You are not accessing the super class variable in the subclass hence super.init() is called followed by the subclass's init. But if you were to try using the super class variable in subclass without calling its initialiser then it will result in a compile time error.
I didn't try this myself, but the Swift Language Guide says:
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.
A simple way to remember this is:
Designated initializers must always delegate up.
Convenience initializers must always delegate across.
So, as it is a 'rule' to call super.init(), it might just be done internally, if not implemented explicitly.

Swift: How to override/forward init call to my singleton initilializer?

I found out that a singleton in swift is easy (credits go to krakendev.io):
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
But I was not able to do this with a subclass and required designated initializers in the superclass. When I try to define all designated initializers private, compiler say that required init method from super must be accessible.
How can I make a subclass (for ex. from UIViewController) to be a singleton with exactly one instance and forward all init-calls to my singleton instance?? I cannot modify self in my init, too('self is immutable').

Adding Convenience Initializers in Swift Subclass

As a learning exercise I am trying to implement a subclass of SKShapeNode that provides a new convenience initializer that takes a number and constructs a ShapeNode that is a square of number width and height.
According to the Swift Book:
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.”
However, the following class doesn't work:
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init(rectOfSize: CGSizeMake(value, value))
}
}
Instead I get:
Playground execution failed: error: <REPL>:34:9: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:34:14: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:35:5: error: self.init isn't called on all paths in delegating initializer
}
My understanding is that MyShapeNode should inherit all of SKShapeNode's convenience initializers because I am not implementing any of my own designated initializers, and because my convenience initializer is calling init(rectOfSize), another convenience initializer, this should work. What am I doing wrong?
There are two problems here:
SKShapeNode has only one designated initializer: init(). This means that we cannot get out of our initializer without calling init().
SKShapeNode has a property path declared as CGPath!. This means that we don't want to get out of our initializer without somehow initializing the path.
The combination of those two things is the source of the issue. In a nutshell, SKShapeNode is incorrectly written. It has a property path that must be initialized; therefore it should have a designated initializer that sets the path. But it doesn't (all of its path-setting initializers are convenience initializers). That's the bug. Putting it another way, the source of the problem is that, convenience or not, the shapeNodeWith... methods are not really initializers at all.
You can, nevertheless, do what you want to do — write a convenience initializer without being forced to write any other initializers — by satisfying both requirements in that order, i.e. by writing it like this:
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init()
self.init(rectOfSize: CGSizeMake(value, value))
}
}
It looks illegal, but it isn't. Once we've called self.init(), we've satisfied the first requirement, and we are now free to refer to self (we no longer get the "use of 'self' in delegating initializer before self.init is called" error) and satisfy the second requirement.
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.
Building on Matt's answer, we had to include an additional function, or else the compiler complained about invoking an initializer with no arguments.
Here's what worked to subclass SKShapeNode:
class CircleNode : SKShapeNode {
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init()
self.init(circleOfRadius: width/2)
// Do stuff
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Good news from 2019! I can report that I now have a SKShape subclass that has the following three initializers:
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init(circleOfRadius: width/2)
self.fillColor = .green
self.position = point
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
and that behaves exactly as expected: when you call the convenience initializer, you get green dots in the desired position. (The double calling of init() as described by #matt and #Crashalot, on the other hand, now results in an error).
I'd prefer to have the ability to modify SKShapeNodes in the .sks scene editor, but you can't have everything. YET.

Resources