Articles, podcasts and news about Swift development, by John Sundell.

Lazy property observers

Published on 06 Oct 2020
Basics article available: Properties

New in Swift 5.3: Property observers can now be attached to lazy properties, which makes it possible for us to automatically observe when a new value was assigned to a given property, even if its value is lazily loaded when first accessed.

As an example, let’s say that an app that we’re working on supports multiple themes, and that we’ve implemented a ThemeController that’s responsible for keeping track of which theme that the user has currently selected, as well as for loading and syncing such Theme values to disk using Foundation’s UserDefaults API.

To make sure that the latest value is always persisted, we’ve added a didSet property observer to our controller’s theme property, but before Swift 5.3, we couldn’t then also make that property lazy — meaning that we’d first have to give it a default value when declaring it, and then load its actual underlying value as part of our controller’s initializer, like this:

class ThemeController {
    var theme = Theme.systemDefault {
        didSet { saveTheme() }
    }

    private let defaults: UserDefaults
    private let defaultsKey = "theme"

    init(defaults: UserDefaults = .standard) {
        self.defaults = defaults
        theme = loadTheme()
    }

    private func loadTheme() -> Theme {
        let savedTheme = defaults
            .string(forKey: defaultsKey)
            .flatMap(Theme.init)

        return savedTheme ?? .systemDefault
    }

    private func saveTheme() {
        defaults.setValue(theme.rawValue, forKey: defaultsKey)
    }
}

But now, when using Swift 5.3, we can make that property load its value lazily when it’s first accessed, which lets us call our loadTheme method directly from within its declaration — giving us a simpler (and possibly slightly faster) implementation:

class ThemeController {
    lazy var theme = loadTheme() {
        didSet { saveTheme() }
    }

    ...

    init(defaults: UserDefaults = .standard) {
        self.defaults = defaults
    }

    ...
}

To learn more about lazy properties in general, check out “Using lazy properties in Swift”.

The above is definitely not a ground-breaking feature that will forever change the way we write Swift code, but it’s another one of those small but really nice improvements that continue to refine how Swift’s various language features can be combined.