Skip to content

Swift Initializers — Complete Guide to Type Initialization

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Swift Initializers. We cover key concepts, practical examples, and best practices to help you master this topic.

Swift initializers ensure that all stored properties have values before an instance is used, supporting designated initializers, convenience initializers for alternative creation paths, required initializers for subclasses, failable initializers that return nil on failure, and automatically generated memberwise initializers for structs. This tutorial covers each initializer type with examples, initialization delegation rules, two-phase initialization for safety, and deinitialization with deinit.

What You'll Learn

  • Creating designated initializers that initialize all stored properties
  • Writing convenience initializers that delegate to designated initializers
  • Using required initializers for classes that must be subclassable
  • Creating failable initializers that return nil
  • Two-phase initialization for safety
  • Deinitialization with deinit for cleanup
  • Automatically generated memberwise initializers for structs
  • Initialization in inheritance hierarchies

Why It Matters

Swift's strict initialization rules prevent uninitialized property access at compile time. Understanding designated vs convenience initializers, two-phase initialization, and failable initializers is essential for creating safe, reusable types. Mistakes in initialization are a common source of runtime crashes.

Real-World Use

Doda Browser's URL model uses a failable initializer that returns nil for invalid URLs. The database model classes use required initializers for Core Data integration. Convenience initializers provide shortcuts for common creation patterns like creating a URL with default query parameters.

Learning Path

flowchart LR
  A[Classes and Structs] --> B[Initializers\nYou are here]
  B --> C[Properties]
  style B fill:#f90,color:#fff

Default Initializers

All stored properties must have values after initialization:

import Foundation

// Struct with default property values
struct Settings {
  var theme = "light"
  var fontSize = 14
  var notificationsEnabled = true
}

let defaultSettings = Settings()
print("Default theme: \(defaultSettings.theme)")

let darkSettings = Settings(theme: "dark", fontSize: 16, notificationsEnabled: false)
print("Dark settings: \(darkSettings.theme), \(darkSettings.fontSize)")

Output:

Default theme: light
Dark settings: dark, 16

Designated Initializers

Designated initializers are the primary initializers for a class:

import Foundation

struct Temperature {
  var celsius: Double

  // Designated initializer (implicit for structs)
  init(celsius: Double) {
    self.celsius = celsius
  }

  // Computed properties
  var fahrenheit: Double {
    return celsius * 9 / 5 + 32
  }

  var kelvin: Double {
    return celsius + 273.15
  }
}

let boiling = Temperature(celsius: 100)
print("100C = \(boiling.fahrenheit)F = \(boiling.kelvin)K")

Output:

100C = 212.0F = 373.15K

Custom Initializers for Classes

Classes can have multiple designated initializers:

import Foundation

class User {
  let username: String
  let email: String
  var age: Int
  var isActive: Bool

  // Designated initializer
  init(username: String, email: String, age: Int, isActive: Bool = true) {
    self.username = username
    self.email = email
    self.age = age
    self.isActive = isActive
  }

  // Convenience initializer - delegates to designated
  convenience init(username: String, email: String) {
    self.init(username: username, email: email, age: 18, isActive: true)
  }

  func description() -> String {
    return "\(username) (\(email), age \(age), active: \(isActive))"
  }
}

let user1 = User(username: "alice", email: "alice@example.com", age: 30)
let user2 = User(username: "bob", email: "bob@example.com")

print(user1.description())
print(user2.description())

Output:

alice (alice@example.com, age 30, active: true)
bob (bob@example.com, age 18, active: true)

Failable Initializers

Initializers that might fail return an optional:

import Foundation

struct ISBN {
  let value: String

  init?(_ value: String) {
    let cleaned = value
      .uppercased()
      .filter { "0123456789X".contains($0) }

    // ISBN-10 or ISBN-13 validation
    let isValid = cleaned.count == 10 || cleaned.count == 13
    if !isValid {
      return nil
    }

    self.value = cleaned
  }
}

let validISBN = ISBN("0-306-40615-2")
let invalidISBN = ISBN("invalid")

print("Valid ISBN: \(validISBN?.value ?? "nil")")
print("Invalid ISBN: \(invalidISBN?.value ?? "nil")")

// Failable initializer for URL-like type
struct WebURL {
  let urlString: String
  let host: String

  init?(_ string: String) {
    guard string.hasPrefix("https://"),
          let hostRange = string.range(of: "://"),
          hostRange.upperBound < string.endIndex else {
      return nil
    }

    let hostStart = string.index(after: hostRange.upperBound)
    guard let slashIndex = string[hostStart...].firstIndex(of: "/") else {
      self.urlString = string
      self.host = String(string[hostStart...])
      return
    }

    self.urlString = string
    self.host = String(string[hostStart..<slashIndex])
  }
}

let valid = WebURL("https://example.com/path")
let invalid = WebURL("ftp://bad.com")

print("Valid URL host: \(valid?.host ?? "nil")")
print("Invalid URL: \(invalid?.host ?? "nil")")

Output:

Valid ISBN: 0306406152
Invalid ISBN: nil
Valid URL host: example.com
Invalid URL: nil

Required Initializers

Subclasses must implement required initializers:

import Foundation

class Animal {
  let name: String

  required init(name: String) {
    self.name = name
  }

  func speak() -> String {
    return "..."
  }
}

class Dog: Animal {
  let breed: String

  required init(name: String) {
    self.breed = "Unknown"
    super.init(name: name)
  }

  init(name: String, breed: String) {
    self.breed = breed
    super.init(name: name)
  }

  override func speak() -> String {
    return "Woof!"
  }
}

let genericAnimal = Animal(name: "Creature")
let dog = Dog(name: "Buddy", breed: "Golden Retriever")

print("\(genericAnimal.name) says: \(genericAnimal.speak())")
print("\(dog.name) the \(dog.breed) says: \(dog.speak())")

Output:

Creature says: ...
Buddy the Golden Retriever says: Woof!

Two-Phase Initialization

Swift ensures all properties are initialized before they are used:

import Foundation

class Shape {
  var color: String

  init(color: String) {
    self.color = color  // Phase 1: initialize all properties
    // Phase 2: customize after initialization
    print("Shape initialized with color: \(color)")
  }
}

class Circle: Shape {
  var radius: Double

  init(color: String, radius: Double) {
    self.radius = radius  // Phase 1: initialize subclass properties first
    super.init(color: color)  // Phase 1: delegate up
    // Phase 2: customize after super.init
    print("Circle initialized with radius: \(radius)")
  }

  convenience init(diameter: Double, color: String) {
    self.init(color: color, radius: diameter / 2)
  }
}

let circle = Circle(diameter: 10, color: "red")

Output:

Shape initialized with color: red
Circle initialized with radius: 5.0

Deinitialization

Classes can define cleanup logic with deinit:

import Foundation

class FileHandle {
  let path: String
  var isOpen = false

  init?(path: String) {
    self.path = path
    if FileManager.default.fileExists(atPath: path) {
      isOpen = true
      print("Opened file: \(path)")
    } else {
      print("File not found: \(path)")
      return nil
    }
  }

  deinit {
    if isOpen {
      isOpen = false
      print("Closed file: \(path)")
    }
  }

  func read() -> String {
    return "File contents of \(path)"
  }
}

do {
  let file = FileHandle(path: "/tmp/test.txt")
  print(file?.read() ?? "Could not read")
}
print("File handle should be closed now")

Output:

File not found: /tmp/test.txt
Could not read
File handle should be closed now

Common Mistakes

  1. Not initializing all stored properties: Swift requires every stored property to have a value after initialization. Missing one causes a compile error.
  2. Calling methods before super.init: Instance methods cannot be called until super.init completes. All properties must be initialized first.
  3. Using convenience init for everything: Convenience initializers must ultimately delegate to a designated initializer. Use designated init for the primary creation path.
  4. Forgetting required init in subclasses: If a superclass marks an init as required, every subclass must implement it.
  5. Not handling failable init failures: The caller must unwrap the optional returned by a failable initializer.

Practice Questions

  1. What is the difference between a designated and convenience initializer?

    • Designated initializers are the primary initializers. Convenience initializers are secondary and must call a designated initializer in the same class.
  2. How does a failable initializer signal failure?

    • By returning nil. Failable initializers are declared with init?.
  3. What is two-phase initialization?

    • Phase 1: initialize all stored properties (subclass first, then superclass). Phase 2: customize properties and call methods.
  4. When would you use required init?

    • When every subclass must provide a specific initialization path. Required init ensures the init exists in all subclasses.
  5. Challenge: Create a BankAccount class with a failable initializer that validates the account number format, a convenience initializer for savings accounts with default interest rate, and deinit that logs the account closure.

Mini Project

Build a validation layer for a form system:

  • Define a FormField struct that uses a failable initializer to validate input based on type (email, phone, zip code, URL).
  • Define a FormModel class with required init that takes a dictionary and converts types.
  • Use convenience init for default values.
  • Log form submission with deinit.
  • Handle initialization failures gracefully with user-friendly error messages.

FAQ

Can structs have deinit?

No. Deinit is only available for classes. Structs are value types and are destroyed when their scope ends.

What is the difference between init? and init!

init? returns an optional. init! returns an implicitly unwrapped optional. Prefer init? for failable initializers.

Can a convenience init call another convenience init?

No. Convenience initializers must call a designated initializer in the same class, not another convenience initializer.

What's Next

Learn how to manage property access and observation in the Properties tutorial.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro