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.
As someone who has worked extensively with Swift, I've gathered many insights over the years. I've compiled them in my book Swift Gems, which is packed with advanced tips and techniques to help intermediate and advanced Swift developers enhance their coding skills. From optimizing collections and handling strings to mastering asynchronous programming and debugging, "Swift Gems" provides practical advice to elevate your Swift development. Grab your copy of Swift Gems and let's explore the advanced techniques together.