DEV Community

Naveen Ragul B
Naveen Ragul B

Posted on • Updated on

Swift - Initialization and Deinitialization

Initialization

Initialization is the process of preparing an instance of a class, structure, or enumeration. Their primary role is to ensure that new instances of a type are correctly initialized before they’re used for the first time.

  • Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created.

  • When a default value is assigned to a stored property, or set its initial value within an initializer, the value of that property is set directly, without calling any property observers.

  • If a property always takes the same initial value, provide a default value rather than setting a value within an initializer.

  • Initializer can be defined using the init keyword.

example :

struct Color {
    let red, green, 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
   }
}
Enter fullscreen mode Exit fullscreen mode
  • Properties of optional type are automatically initialized with a value of nil.

  • For class instances, a constant property can be assigned during initialization only by the class that introduces it. It can’t be modified by a subclass.

  • Default initializer are created for structure or class that provides default values for all of its properties and doesn’t provide at least one initializer itself.

  • Memberwise Initializers for Structure Types - Structure types automatically receive a memberwise initializer if they don’t define any of their own custom initializers.

  • if you define a custom initializer for a value type, you will no longer have access to the default initializer and the memberwise initializer.

For a value type to be initializable with the default initializer and memberwise initializer, and also with custom initializers, include custom initializers in an extension rather than as part of the value type’s original implementation.


Initializer Delegation

Initializers can call other initializers to perform part of an instance’s initialization. This process, known as initializer delegation, avoids duplicating code across multiple initializers.

example :

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)
    }
}

Enter fullscreen mode Exit fullscreen mode

Class Inheritance and Initialization

All of a class’s stored properties—including any properties the class inherits from its superclass—must be assigned an initial value during initialization.

  • Below two kinds of initializers for class types ensures all stored properties receive an initial value.
    1. Designated Initializers
    2. Convenience Initializers

Designated Initializer

Designated initializers are the primary initializers that fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process 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.
syntax :

init(parameters) {
    statements
}
Enter fullscreen mode Exit fullscreen mode

Convenience Initializer

Convenience initializers are secondary, that call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values.
syntax :

convenience init(parameters) {
    statements
}
Enter fullscreen mode Exit fullscreen mode

Rules - Initializer Delegation for Class Types

  1. A designated initializer must call a designated initializer from its immediate superclass.
  2. A convenience initializer must call another initializer from the same class.
  3. A convenience initializer must ultimately call a designated initializer.
  • Designated initializers must always delegate up.
  • Convenience initializers must always delegate across.

Two-Phase Initialization

Class initialization in Swift is a two-phase process.

  1. In the first phase, each stored property is assigned an initial value by the class that introduced it.
  2. Each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.

Swift’s compiler performs four helpful safety-checks to make sure that two-phase initialization is completed without error.


Initializer Inheritance and Overriding

  • If a Subclass initializer matches a superclass designated initializer, you are effectively providing an override of that designated initializer. Therefore, you must write the override modifier before the subclass’s initializer definition.

  • You always write the override modifier when overriding a superclass designated initializer, even if your subclass’s implementation of the initializer is a convenience initializer.

  • If a subclass initializer matches a superclass convenience initializer, that superclass convenience initializer can never be called directly by your subclass, as per the rules described above in Initializer Delegation for Class Types. Therefore, your subclass is not providing an override of the superclass initializer. As a result, you don’t write the override modifier.


Automatic Initializer Inheritance

  • Subclasses don’t inherit their superclass initializers by default. However, superclass initializers are automatically inherited if certain conditions are met.
  1. If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
  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.

example :

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}
Enter fullscreen mode Exit fullscreen mode
class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}
Enter fullscreen mode Exit fullscreen mode
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
Enter fullscreen mode Exit fullscreen mode

Failable Initializers

  • For class, structure, or enumeration - initialization can fail. use init? keyword for Faible in initializer.

  • A failable initializer creates an optional value of the type it initializes. Return nil within a failable initializer to indicate initialization failure.

example :

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Enumerations with raw values automatically receive a failable initializer, init?(rawValue:), that takes a parameter called rawValue of the appropriate raw-value type and selects a matching enumeration case if one is found, or triggers an initialization failure if no matching value exists.

example :

enum TemperatureUnit: Character {
    case kelvin = "K", celsius = "C", fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
Enter fullscreen mode Exit fullscreen mode
  • A failable initializer can delegate across to another failable initializer from the same class, structure, or enumeration. Similarly, a subclass failable initializer can delegate up to a superclass failable initializer.

  • A failable initializer can also delegate to a nonfailable initializer. Use this approach if you need to add a potential failure state to an existing initialization process that doesn’t otherwise fail.

  • superclass failable initializer can be overrided in a subclass as Failable Initializer or Non Failable Initializer.
    but not the otherway.

  • if you override a failable superclass initializer with a nonfailable subclass initializer, the only way to delegate up to the superclass initializer is to force-unwrap the result of the failable superclass initializer.

  • init! Failable Initializer - It creates an implicitly unwrapped optional instance.

  • we can delegate from init? to init! and vice versa, and you can override init? with init! and vice versa. You can also delegate from init to init!.


Required Initializer

  • Use required modifier for required initializer. Every subclass must implement initializer from base class if base class has required initializer.

  • Required modifier should be used before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain.

  • override modifier is not required when overriding a required designated initializer.

example :

class SomeClass {
    required init() {
        // initializer implementation 
    }
}
Enter fullscreen mode Exit fullscreen mode
class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation 
    }
}
Enter fullscreen mode Exit fullscreen mode

Setting a Default Property Value with a Closure or Function

  • Closures or Global function can be used to provide stored property’s default value.

example :

class SomeClass {
    let someProperty: SomeType = {
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
    }()
}
Enter fullscreen mode Exit fullscreen mode

Closure’s end curly brace is followed by an empty pair of parentheses. This tells Swift to execute the closure immediately. If you omit these parentheses, you are trying to assign the closure itself to the property, and not the return value of the closure.


Deinitialization

  • A deinitializer is called immediately before a class instance is deallocated. Swift automatically deallocates your instances when they’re no longer needed, to free up resources.

  • Use deinitializer when you are working with your own resources, you might need to perform some additional cleanup.

  • Class definitions can have at most one deinitializer per class. The deinitializer doesn’t take any parameters and is written without parentheses:
    syntax :

deinit {
    // perform the deinitialization
}
Enter fullscreen mode Exit fullscreen mode
  • You aren’t allowed to call a deinitializer yourself. Superclass deinitializers are inherited by their subclasses, and the superclass deinitializer is called automatically at the end of a subclass deinitializer implementation.

Top comments (0)