Swift Initialization Process
Initialization is the process of preparing to use a class, structure, or enumeration type instance. This process involves setting initial values for each property in the instance and performing any necessary setup or initialization tasks.
Swift initializers use the init()
method.
Unlike constructors in Objective-C, Swift initializers do not return a value. Their primary task is to ensure that new instances are correctly initialized before they are used for the first time.
Class instances can also define deinitializers to perform memory cleanup tasks before the class instance is deallocated.
Initial Assignment of Stored Properties
Classes and structures must set appropriate initial values for all stored properties when an instance is created.
When stored properties are assigned values in an initializer, their values are set directly and do not trigger any property observers.
Stored properties assignment process in initializers:
- Create initial values.
- Specify default property values in the property definition.
- Initialize the instance and call the
init()
method.
Initializers
Initializers are called when creating a new instance of a specific type. They are similar in form to instance methods without any parameters, named with the keyword init
.
Syntax
init() {
// Code to execute after instance creation
}
Example
The following structure defines an initializer init
without parameters, which initializes the stored properties length
and breadth
to 6 and 12, respectively:
struct Rectangle {
var length: Double
var breadth: Double
init() {
length = 6
breadth = 12
}
}
var area = Rectangle()
print("Rectangle area is \(area.length * area.breadth)")
Output of the above program:
Rectangle area is 72.0
Default Property Values
You can set initial values for stored properties within the initializer; alternatively, you can set default values during property declaration.
Using default values can make your initializer more concise and clear, and allow automatic inference of property types from the default values.
The following example sets default values during property declaration:
struct Rectangle {
// Set default values
var length = 6
var breadth = 12
}
var area = Rectangle()
print("Rectangle area is \(area.length * area.breadth)")
Output of the above program:
Rectangle area is 72
Initializer Parameters
You can provide initializer parameters when defining the init()
method, as shown below:
struct Rectangle {
var length: Double
var breadth: Double
var area: Double
init(fromLength length: Double, fromBreadth breadth: Double) {
self.length = length
self.breadth = breadth
area = length * breadth
}
init(fromLeng leng: Double, fromBread bread: Double) {
self.length = leng
self.breadth = bread
area = leng * bread
}
}
let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("Area is: \(ar.area)")
let are = Rectangle(fromLeng: 36, fromBread: 12)
print("Area is: \(are.area)")
Output of the above program:
Area is: 72.0
Area is: 432.0
Internal and External Parameter Names
Like function and method parameters, initializer parameters have an internal parameter name used within the initializer and an external parameter name used when calling the initializer.
However, unlike functions and methods, initializers do not have a distinguishable name before the parentheses. Therefore, the initializer to be called is determined primarily by the parameter names and types within the initializer.
If you do not provide an external name for an initializer parameter, Swift automatically generates an external name that matches the internal name.
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
struct Color {
var red: Double
var green: Double
var blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
// Creating a new Color instance by passing values for the three external parameters and calling the initializer
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
print("red value is: \(magenta.red)")
print("green value is: \(magenta.green)")
print("blue value is: \(magenta.blue)")
// Creating a new Color instance by passing values for the three external parameters and calling the initializer
let halfGray = Color(white: 0.5)
print("red value is: \(halfGray.red)")
print("green value is: \(halfGray.green)")
print("blue value is: \(halfGray.blue)")
The output of the above program is:
red value is: 1.0
green value is: 0.0
blue value is: 1.0
red value is: 0.5
green value is: 0.5
blue value is: 0.5
Without External Name Parameters
If you do not want to provide an external name for an initializer parameter, you can use an underscore _
to explicitly describe its external name.
struct Rectangle {
var length: Double
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
// Without providing an external name
init(_ area: Double) {
length = area
}
}
// Calling without providing an external name
let rectarea = Rectangle(180.0)
print("Area is: \(rectarea.length)")
// Calling without providing an external name
let rearea = Rectangle(370.0)
print("Area is: \(rearea.length)")
// Calling without providing an external name
let recarea = Rectangle(110.0)
print("Area is: \(recarea.length)")
The output of the above program is:
Area is: 180.0
Area is: 370.0
Area is: 110.0
Optional Property Types
If your custom type includes a stored property that logically allows for a nil value, you need to define it as an optional type.
When a stored property is declared as optional, it will be automatically initialized to nil.
struct Rectangle {
var length: Double?
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
init(_ area: Double) {
length = area
}
}
let rectarea = Rectangle(180.0)
print("Area is: \(rectarea.length)")
let rearea = Rectangle(370.0)
print("Area is: \(rearea.length)")
let recarea = Rectangle(110.0)
print("Area is: \(recarea.length)")
The output of the above program is:
Area is: Optional(180.0)
Area is: Optional(370.0)
Area is: Optional(110.0)
Modifying Constant Properties During Initialization
You can modify the value of a constant property at any point during the initialization process as long as it is set to a definite value by the time initialization finishes.
For an instance of a class, its constant properties can only be modified during initialization in the class where they are defined; they cannot be modified in a subclass.
Even though the length
property is now a constant, we can still set its value in the class's initializer:
struct Rectangle {
let length: Double?
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
init(_ area: Double) {
length = area
}
}
let rectarea = Rectangle(180.0)
print("Area is: \(rectarea.length)")
let rearea = Rectangle(370.0)
print("Area is: \(rearea.length)")
let recarea = Rectangle(110.0)
print("Area is: \(recarea.length)")
The output of the above program is:
Area is: Optional(180.0)
Area is: Optional(370.0)
Area is: Optional(110.0)
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
init(_ area: Double) {
length = area
}
}
let rectarea = Rectangle(180.0)
print("Area is: \(rectarea.length)")
let rearea = Rectangle(370.0)
print("Area is: \(rearea.length)")
let recarea = Rectangle(110.0)
print("Area is: \(recarea.length)")
The above program execution output is:
Area is: Optional(180.0)
Area is: Optional(370.0)
Area is: Optional(110.0)
Default Initializer
The default initializer will simply create an instance with all property values set to their default values:
In the following example, the ShoppingListItem class has default values for all its properties and is a base class without a superclass, so it will automatically get a default initializer that sets all properties to their default values.
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
print("Name is: \(item.name)")
print("Quantity is: \(item.quantity)")
print("Purchased: \(item.purchased)")
The above program execution output is:
Name is: nil
Quantity is: 1
Purchased: false
Memberwise Initializers for Structure Types
If a structure type provides default values for all its stored properties and does not provide a custom initializer, it automatically receives a memberwise initializer.
When you call the memberwise initializer, you can initialize the member properties by passing parameters with the same names as the properties.
The following example defines a structure Rectangle, which includes two properties length and breadth. Swift can infer their type as Double based on their initial values 100.0 and 200.0.
struct Rectangle {
var length = 100.0, breadth = 200.0
}
let area = Rectangle(length: 24.0, breadth: 32.0)
print("Rectangle area: \(area.length)")
print("Rectangle area: \(area.breadth)")
Since both stored properties have default values, the structure Rectangle automatically receives a memberwise initializer init(width:height:). You can use it to create a new instance of Rectangle.
The above program execution output is:
Rectangle area: 24.0
Rectangle area: 32.0
Initializer Delegation for Value Types
Initializers can call other initializers to perform part of the instance initialization. This process is known as initializer delegation and can reduce code duplication among multiple initializers.
In the following example, the Rect structure delegates the initialization process to Size and Point:
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
// Both origin and size properties use the default values Point(x: 0.0, y: 0.0) and Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size struct initial values: \(basicRect.size.width, basicRect.size.height) ")
print("Rect struct initial values: \(basicRect.origin.x, basicRect.origin.y) ")
// Assign parameter values to the corresponding stored properties
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
print("Size struct initial values: \(originRect.size.width, originRect.size.height) ")
print("Rect struct initial values: \(originRect.origin.x, originRect.origin.y) ")
// First calculate the origin coordinates from the values of center and size.
// Then call (or delegate to) the init(origin:size:) initializer to assign the new origin and size values to the corresponding properties
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
print("Size struct initial values: \(centerRect.size.width, centerRect.size.height) ")
print("Rect struct initial values: \(centerRect.origin.x, centerRect.origin.y) ")
The program output is as follows:
Size struct initial values: (0.0, 0.0)
Rect struct initial values: (0.0, 0.0)
Size struct initial values: (5.0, 5.0)
Rect struct initial values: (2.0, 2.0)
Size struct initial values: (3.0, 3.0)
Rect struct initial values: (2.5, 2.5)
Constructor Delegation Rules
Value Types | Class Types |
---|---|
Do not support inheritance, so the process of constructor delegation is relatively simple, as they can only delegate to other constructors provided by themselves. You can use self.init to reference other constructors of the same value type in a custom constructor. |
They can inherit from other classes, which means that classes are responsible for ensuring that all inherited stored properties are also correctly initialized during construction. |
Class Inheritance and Construction Process
Swift provides two types of class constructors to ensure that all stored properties in class instances are initialized, namely designated constructors and convenience constructors.
Designated Constructor | Convenience Constructor |
---|---|
The main constructor in the class | A secondary, auxiliary constructor in the class |
Initializes all properties provided by the class and calls constructors of the superclass chain to initialize the superclass. | Convenience constructors can be defined to call a designated constructor in the same class and provide default values for its parameters. You can also define convenience constructors to create instances for specific purposes or specific inputs. |
Every class must have at least one designated constructor | Provide convenience constructors only when necessary |
init(parameters) { statements } |
convenience init(parameters) { statements } |
Designated Constructor Example
class mainClass {
var no1 : Int // Local stored variable
init(no1 : Int) {
self.no1 = no1 // Initialization
}
}
class subClass : mainClass {
var no2 : Int // New subclass stored variable
init(no1 : Int, no2 : Int) {
self.no2 = no2 // Initialization
super.init(no1:no1) // Superclass initialization
}
}
let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)
print("res is: \(res.no1)")
print("res2 is: \(res2.no1)")
print("res2 is: \(res2.no2)")
The program output is as follows:
res is: 10
res2 is: 10
res2 is: 20
Convenience Constructor Example
class mainClass {
var no1 : Int // Local stored variable
init(no1 : Int) {
self.no1 = no1 // Initialization
}
class subClass: mainClass {
var no2: Int
init(no1: Int, no2: Int) {
self.no2 = no2
super.init(no1: no1)
}
// Convenience method requires only one parameter
override convenience init(no1: Int) {
self.init(no1: no1, no2: 0)
}
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)
print("res is: \(res.no1)")
print("res2 is: \(res2.no1)")
print("res2 is: \(res2.no2)")
The above program execution output is:
res is: 20
res2 is: 30
res2 is: 50
Inheritance and Overriding of Constructors
Subclasses in Swift do not inherit their parent class constructors by default.
Parent class constructors are only inherited under certain and safe conditions.
When you override a parent class designated constructor, you need to use the override
modifier.
class SuperClass {
var corners = 4
var description: String {
return "\(corners) sides"
}
}
let rectangle = SuperClass()
print("Rectangle: \(rectangle.description)")
class SubClass: SuperClass {
override init() { // Overriding constructor
super.init()
corners = 5
}
}
let subClass = SubClass()
print("Pentagon: \(subClass.description)")
The above program execution output is:
Rectangle: 4 sides
Pentagon: 5 sides
Designated and Convenience Constructors Example
The following example demonstrates how designated constructors, convenience constructors, and automatic constructor inheritance interact.
It defines a class hierarchy containing two classes, MainClass
and SubClass
, and shows how their constructors interact.
class MainClass {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Anonymous]")
}
}
let main = MainClass(name: "tutorialpro")
print("MainClass name is: \(main.name)")
let main2 = MainClass()
print("No corresponding name: \(main2.name)")
class SubClass: MainClass {
var count: Int
init(name: String, count: Int) {
self.count = count
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, count: 1)
}
}
let sub = SubClass(name: "tutorialpro")
print("MainClass name is: \(sub.name)")
let sub2 = SubClass(name: "tutorialpro", count: 3)
print("count variable: \(sub2.count)")
The above program execution output is:
MainClass name is: tutorialpro
No corresponding name: [Anonymous]
MainClass name is: tutorialpro
count variable: 3
Failable Constructors for Classes
If a class, structure, or enumeration type object might fail during its construction, you can define a failable constructor for it.
Variables might fail to initialize due to:
- Invalid parameter values.
- Missing required external resources.
- Unmet conditions.
To handle such potential failures during construction, you can add one or more failable constructors to the definition of a class, structure, or enumeration type. The syntax involves adding a question mark after the init
keyword (init?
).
Example
class MainClass {
var name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
let main = MainClass(name: "tutorialpro")
if let main = main {
print("MainClass name is: \(main.name)")
}
let main2 = MainClass(name: "")
if main2 == nil {
print("MainClass instance is nil")
}
The above program execution output is:
MainClass name is: tutorialpro
MainClass instance is nil
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
// Create an Animal object using the failable initializer and check if it was successful
// The type of someCreature is Animal? instead of Animal
let someCreature = Animal(species: "Giraffe")
// Print "Animal initialized as Giraffe"
if let giraffe = someCreature {
print("Animal initialized as \(giraffe.species)")
}
Output of the above program:
Animal initialized as Giraffe
Failable Initializers for Enumerations
You can create a failable initializer with one or more parameters to get a specific enumeration member.
Example
In this example, an enumeration named TemperatureUnit
is defined. It includes three possible members (Kelvin
, Celsius
, and Fahrenheit
) and a failable initializer to find the enumeration member corresponding to a Character
value:
enum TemperatureUnit {
// Kelvin, Celsius, Fahrenheit
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
Output of the above program:
This is a defined temperature unit, so initialization succeeded.
This is not a defined temperature unit, so initialization failed.
Failable Initializers for Classes
Failable initializers for value types (such as structs or enumerations) can trigger failure at any point.
However, failable initializers for classes can only trigger failure after all class properties have been initialized and all constructor delegation within the class has completed.
Example
In this example, a class named StudRecord
is defined. Since the studname
property is a constant, once the StudRecord
class is successfully constructed, the studname
property will definitely have a non-nil value.
class StudRecord {
let studname: String!
init?(studname: String) {
self.studname = studname
if studname.isEmpty { return nil }
}
}
if let stname = StudRecord(studname: "Failable Initializer") {
print("Module is \(stname.studname)")
}
Output of the above program:
Module is Failable Initializer
Overriding a Failable Initializer
Like other initializers, you can override a failable initializer in a subclass.
You can also override a failable initializer with a non-failable initializer in a subclass.
You can override a failable initializer with a non-failable initializer, but not vice versa.
A non-failable initializer can never delegate to a failable initializer.
Example
The following example demonstrates failable and non-failable initializers:
class Planet {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[No Name]")
}
}
class NamedPlanet: Planet {
override init(name: String) {
super.init(name: name.isEmpty ? "[No Name]" : name)
}
}
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[No Planets]")
}
}
let plName = Planet(name: "Mercury")
print("The name of the planet is: \(plName.name)")
let noplName = Planet()
print("Planet with no name: \(noplName.name)")
class Planets: Planet {
var count: Int
init(name: String, count: Int) {
self.count = count
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, count: 1)
}
}
The output of the program execution is:
The name of the planet is: Mercury
Planet with no name: [No Planets]
Failable Initializer init!
Typically, we define a failable initializer by adding a question mark after the init keyword (init?). However, you can also define a failable initializer by adding an exclamation mark after the init keyword (init!). Here is an example:
struct StudRecord {
let stname: String
init!(stname: String) {
if stname.isEmpty { return nil }
self.stname = stname
}
}
let stmark = StudRecord(stname: "tutorialpro")
if let name = stmark {
print("Student name is specified")
}
let blankname = StudRecord(stname: "")
if blankname == nil {
print("Student name is empty")
}
The output of the program execution is:
Student name is specified
Student name is empty