Initialisation - RaduG/swift_learning GitHub Wiki

General rules

  • Initialisers can overload on types, argument labels etc.
  • Any class or struct that has default values for all its properties automatically gets an initialiser with no arguments.
  • Any struct that does not explicitly implement any initialisers automatically gets a member-wise initialiser. If only some of the properties have default values, only those without may be passed to this constructor if the default values are fine for the others.

Designated initialisers

Designated initialisers are initialisers that must not leave any properties unset. They must call one of their immediate superclass's initialisers after setting all properties they themselves declare/override are set.

class AutomaticCar: Car {
  let n_gears: Int
  override var currentSpeed: Double {
    didSet {
      if Int(currentSpeed) == 0 {
        self.gear = 0
      } else {
        self.gear = min(Int(currentSpeed / 10) + 1, n_gears)
      }
    }
  }

  init(topSpeed: Double, gears: Int) {
    self.n_gears = gears
    super.init(topSpeed: topSpeed)
  }
}

Convenience initialisers

Convenience initialisers must call another initialiser in the same class and must ultimately call a designated initialiser. They are marked by the convenience keyword.

class AutomaticCar: Car {
  let n_gears: Int
  override var currentSpeed: Double {
    didSet {
      if Int(currentSpeed) == 0 {
        self.gear = 0
      } else {
        self.gear = min(Int(currentSpeed / 10) + 1, n_gears)
      }
    }
  }

  init(topSpeed: Double, gears: Int) {
    self.n_gears = gears
    super.init(topSpeed: topSpeed)
  }

  convenience init(nationalSpeedLimit: Double, gears: Int) {
    self.init(topSpeed: nationalSpeedLimit + 10.0, gears: gears)
  }
}

Initialiser inheritance

Subclasses do not inherit initialisers by default. However, there are circumstances when superclass initialisers are automatically inherited:

  • If a subclass does not define any initialisers, then it will automatically inherit all the designated initialisers of its superclass
  • If a subclass overrides all initialisers of its superclass, then it will automatically inherit all the convenience initialisers of its superclass

Failable initialisers

Failable initialisers can be used to sometimes fail initialisation. A failable initialiser cannot have the same signature as a normal initialiser. Use init? to declare a failable initialiser and return nil to fail initialisation.

class AutomaticCar: Car {
  let n_gears: Int
  override var currentSpeed: Double {
    didSet {
      if Int(currentSpeed) == 0 {
        self.gear = 0
      } else {
        self.gear = min(Int(currentSpeed / 10) + 1, n_gears)
      }
    }
  }

  init?(topSpeed: Double, gears: Int) {
    guard gears > 0 else {
        return nil
    }
    self.n_gears = gears
    super.init(topSpeed: topSpeed)
  }

  convenience init?(nationalSpeedLimit: Double, gears: Int) {
    self.init(topSpeed: nationalSpeedLimit + 10.0, gears: gears)
  }
}

You can override a failable initialiser with a nonfailable initialiser but not the other way around.

init! is an alternative to init? which implicitly unwraps the instance:

class AutomaticCar: Car {
  let n_gears: Int
  override var currentSpeed: Double {
    didSet {
      if Int(currentSpeed) == 0 {
        self.gear = 0
      } else {
        self.gear = min(Int(currentSpeed / 10) + 1, n_gears)
      }
    }
  }

  init!(topSpeed: Double, gears: Int) {
    guard gears > 0 else {
        return nil
    }
    self.n_gears = gears
    super.init(topSpeed: topSpeed)
  }

  convenience init(nationalSpeedLimit: Double, gears: Int) {
    self.init(topSpeed: nationalSpeedLimit + 10.0, gears: gears)
  }
}

// this will raise a runtime error
let car = AutomaticCar(topSpeed: 100, gears: 0)

Required initialisers

Required initialisers are used to require all subclasses to implement the initialiser with that signature (unless automatically inherited!). Subclasses must also declare those as required. The override keyword is not necessary when implementing required initialisers. Use the required keyword.

Dynamic default value

The swift pattern for providing computed default values for properties (e.g. random number) is to define their value as a closure and call it.

class Person {
  var age: Int = {
    Int.random(in: 1...100)
  }()

  init() {}

  init(age: Int) {
    self.age = age
  }
}

let p1 = Person()
print(p1.age)
let p2 = Person()
print(p2.age)