Welcome to our deep dive into Swift Initializer Requirements! In this tutorial, we'll explore the essentials of initializing Swift classes, structures, and enumerations, making you well-prepared for real-world Swift projects.
<a name="understanding-initializers"></a>
Initializers are special methods that Swift uses to create and initialize instances of a class, structure, or enumeration. When you create a new instance, an initializer is called to set up the properties with starting values.
<a name="defining-initializers"></a>
An initializer in Swift has the same syntax as a method. However, an initializer has no return type, and its name matches the name of the struct, class, or enumeration it initializes.
Here's an example of a simple structure with an initializer:
struct Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}If you don't define any initializers for a struct or class, Swift provides a default initializer that sets all properties to their default values.
For example, a struct with a default initializer:
struct DefaultPerson: Identifiable {
let id = UUID()
var name = ""
}When you want to customize the default initializer or create multiple initializers for a class, you'll use designated initializers. A designated initializer is the initializer that another initializer calls when it needs to delegate initialization to another initializer.
class Employee {
let id: UUID
let name: String
let position: String
init(id: UUID, name: String) {
self.id = id
self.name = name
self.position = "Employee"
}
init(id: UUID, name: String, position: String) {
self.id = id
self.name = name
self.position = position
}
convenience init(name: String) {
self.init(id: UUID(), name: name, position: "Employee")
}
}In the example above, the init(id: UUID, name: String, position: String) is the designated initializer, while init(name: String) is a convenience initializer that delegates initialization to the designated initializer.
If a class has stored properties with no initial value, Swift requires you to provide a designated initializer.
class Car {
let brand: String
var model: String
var year: Int
init(brand: String, model: String) {
self.brand = brand
self.model = model
self.year = 2022
}
}Convenience initializers are used when you want to provide an easier way to initialize an instance. They don't need to assign values to all properties. Instead, they delegate the initialization to another initializer.
struct Address {
let street: String
let city: String
let state: String
let postalCode: String
init(street: String, city: String, state: String, postalCode: String) {
self.street = street
self.city = city
self.state = state
self.postalCode = postalCode
}
convenience init(street: String, city: String, postalCode: String) {
self.init(street: street, city: city, state: "California", postalCode: postalCode)
}
}Custom initializers allow you to set properties based on different conditions or requirements.
class Vehicle {
let brand: String
let type: String
var currentSpeed: Int
init(brand: String, type: String) {
self.brand = brand
self.type = type
self.currentSpeed = 0
}
func initWithSpeed(brand: String, type: String, speed: Int) {
self.init(brand: brand, type: type)
self.currentSpeed = speed
}
}<a name="initializer-rules-and-best-practices"></a>
When defining initializers in a subclass, you should always call a superclass initializer to ensure proper initialization of the superclass.
class Vehicle {
let brand: String
let type: String
var currentSpeed: Int
init(brand: String, type: String) {
self.brand = brand
self.type = type
self.currentSpeed = 0
}
}
class Car: Vehicle {
var numberOfDoors: Int
init(brand: String, type: String, numberOfDoors: Int) {
self.numberOfDoors = numberOfDoors
super.init(brand: brand, type: type)
}
}Nested initializers are used when a class, struct, or enumeration needs to be initialized based on the initialization of another class, struct, or enumeration.
enum Shape {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
func area() -> Double {
switch self {
case .circle(let radius):
return Double.pi * radius * radius
case .rectangle(let width, let height):
return width * height
}
}
}
struct Point {
let x: Double
let y: Double
init(x: Double, y: Double) {
self.x = x
self.y = y
}
init(point: Point, shape: Shape) {
switch shape {
case .circle(let radius):
self.init(x: radius, y: 0)
case .rectangle(let width, let height):
self.init(x: 0, y: height)
}
}
}If an initializer encounters an error that prevents the instance from being properly initialized, it should throw an error to signal the failure.
struct Email {
let address: String
init?(address: String) {
guard address.contains("@") else {
return nil
}
self.address = address
}
}You can chain initializers to create complex instances with multiple properties. This is especially useful when initializing nested data structures.
struct Person {
let firstName: String
let lastName: String
let address: Address
init(firstName: String, lastName: String, street: String, city: String, postalCode: String) {
self.firstName = firstName
self.lastName = lastName
self.address = Address(street: street, city: city, postalCode: postalCode)
}
}<a name="quiz"></a>
What is the difference between a designated initializer and a convenience initializer in Swift?