NEW BOOK! Swift Gems: 100+ tips to take your Swift code to the next level. Learn more ...NEW BOOK! Swift Gems:100+ advanced Swift tips. Lear more...

Trigger property observers from initializers in Swift

In Swift, property observers such as willSet and didSet are not called when a property is set in an initializer. This is by design, as the initializer's purpose is to set up the initial state of an object, and during this phase, the object is not yet fully initialized. However, if we need to perform some actions similar to what we'd do in property observers during initialization, there are some workarounds.

# Set properties after initialization

One approach is to set the properties after the object has been initialized.

class MyClass {
    var myProperty: String {
        willSet {
            print("Will set myProperty to \(newValue)")
        }
        didSet {
            print("Did set myProperty to \(myProperty), previously \(oldValue)")
        }
    }

    init() {
        myProperty = "Initial value"
    }
}

let myObject = MyClass()
myObject.myProperty = "New value"

In this example, the property observers will not trigger during the initial assignment in the initializer but will trigger on subsequent property changes.

# Separate property setup method

Another approach is to use a separate method to set up the property.

class MyClass {
    var myProperty: String {
        willSet {
            print("Will set myProperty to \(newValue)")
        }
        didSet {
            print("Did set myProperty to \(myProperty), previously \(oldValue)")
        }
    }
    
    init(value: String) {
        myProperty = "Initial value"
        setupPropertyValue(value: value)
    }
    
    private func setupPropertyValue(value: String) {
        myProperty = value
    }
}

let myObject = MyClass(value: "New value")

This approach ensures that the property observers are triggered during the setup phase after the initialization of the object is completed.

# Create a defer closure

An alternative approach involves using a defer block within the initializer.

class MyClass {
    var myProperty: String {
        willSet {
            print("Will set myProperty to \(newValue)")
        }
        didSet {
            print("Did set myProperty to \(myProperty), previously \(oldValue)")
        }
    }

    init(value: String) {
        defer { myProperty = value } 
        myProperty = "Initial value"
    }
}

let myObject = MyClass(value: "New value")

The defer block ensures that the didSet logic is called after the initial value is set.

# Manually trigger side effects

If we need to perform some specific actions during initialization, we can also manually call the same methods or code blocks that we would have called in our observers.

class MyClass {
    var myProperty: String {
        willSet {
            propertyWillChange(newValue)
        }
        didSet {
            propertyDidChange(oldValue)
        }
    }

    init(value: String) {
        myProperty = value
        propertyDidChange(nil)
    }

    private func propertyWillChange(_ newValue: String) {
        print("Will set myProperty to \(newValue)")
    }

    private func propertyDidChange(_ oldValue: String?) {
        print("Did set myProperty to \(myProperty), previously \(oldValue)")
    }
}

let myObject = MyClass(value: "New value")

In this example, the propertyDidChange() method is manually called within the initializer to simulate the didSet observer.


While property observers don't fire during initialization, these workarounds offer alternative ways to achieve similar behavior. We choose the approach that best fits our use case and coding style.

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Check out our new book!

Swift Gems

100+ tips to take your Swift code to the next level

Swift Gems

100+ tips to take your Swift code to the next level

  • Advanced Swift techniques for experienced developers bypassing basic tutorials
  • Curated, actionable tips ready for immediate integration into any Swift project
  • Strategies to improve code quality, structure, and performance across all platforms
  • Practical Swift insights from years of development, applicable from iOS to server-side Swift