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

Attaching property wrappers to function arguments

Published on 23 Jul 2021
Basics article available: Properties

New in Swift 5.5: Property wrappers can now be applied directly to function arguments, just like how they can be used to add additional functionality to a property or local variable.

For example, let’s say that an app that we’re working on contains a function that saves a Photo for a given name, and that we always want to normalize each name by lowercasing it. One way to do that would be to override the passed name argument using a local property — like this:

func savePhoto(_ photo: Photo, named name: String) {
    let name = name.lowercased()
    ...
}

There’s certainly nothing wrong with the above approach, but let’s take a look at how we could now use a property wrapper to instead embed our lowercasing transformation into the function argument itself.

To get started, let’s create a Lowercased property wrapper, which automatically lowercases any String value that was assigned to it:

@propertyWrapper struct Lowercased {
    var wrappedValue: String {
        didSet {
            wrappedValue = wrappedValue.lowercased()
        }
    }

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue.lowercased()
    }
}

Then, we can now attach our new Lowercased wrapper directly to our name argument, which will ensure that its value will always be lowercased, without requiring that transformation to happen inline within our function’s main body:

func savePhoto(_ photo: Photo, @Lowercased named name: String) {
    ...
}

Pretty neat! Even property wrappers that accept arguments of their own can be attached using the above technique. For example, the following Truncated wrapper automatically truncates its wrapped collection so that it only contains a certain number of elements:

@propertyWrapper struct Truncated<Value: RangeReplaceableCollection> {
    var wrappedValue: Value {
        didSet { wrappedValue = Value(wrappedValue.prefix(maxLength)) }
    }
    var maxLength: Int

    init(wrappedValue: Value, maxLength: Int) {
        self.wrappedValue = Value(wrappedValue.prefix(maxLength))
        self.maxLength = maxLength
    }
}

With the above in place, we can now easily truncate any collection-based function argument simply by attaching our new Truncated wrapper to it — like this:

func updateFavorites(@Truncated(maxLength: 20) to favorites: [Item]) {
    ...
}

While this new feature doesn’t really provide us with any revolutionary new ways to write Swift code, it does add an extra degree of consistency to Swift’s overall property wrappers system — since wrappers can now be applied to properties, local variables, and function arguments, all with the same kind of functionality.