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.

Integrating SwiftUI into UIKit Apps by Natalia Panferova book coverIntegrating SwiftUI into UIKit Apps by Natalia Panferova book cover

Check out our book!

Integrating SwiftUI into UIKit Apps

Integrating SwiftUI intoUIKit Apps

UPDATED FOR iOS 17!

A detailed guide on gradually adopting SwiftUI in UIKit projects.

  • Discover various ways to add SwiftUI views to existing UIKit projects
  • Use Xcode previews when designing and building UI
  • Update your UIKit apps with new features such as Swift Charts and Lock Screen widgets
  • Migrate larger parts of your apps to SwiftUI while reusing views and controllers built in UIKit