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

Chained implicit member expressions in Swift 5.4

Published on 09 Feb 2021

New in Swift 5.4: Implicit member expressions (also known as “dot syntax”) can now be used even when accessing a property or method on the result of such an expression, as long as the final return type remains the same.

What that means in practice is that whenever we’re creating an object or value using a static API, or when referencing an enum case, we can now directly call a method or property on that instance, and the compiler will still be able to infer what type that we’re referring to.

As an example, when creating a UIColor instance using one of the built-in, static APIs that ship as part of the system, we can now easily modify the alpha component of such a color without having to explicitly refer to UIColor itself in situations like this:

// In Swift 5.3 and earlier, an explicit type reference is always
// required when dealing with chained expressions:
let view = UIView()
view.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
...

// In Swift 5.4, the type of our expression can now be inferred:
let view = UIView()
view.backgroundColor = .blue.withAlphaComponent(0.5)
...

Of course, the above approach also works when using our own static APIs as well, for example any custom UIColor definitions that we’ve added by using an extension:

extension UIColor {
    static var chiliRed: UIColor {
        UIColor(red: 0.89, green: 0.24, blue: 0.16, alpha: 1)
    }
}

let view = UIView()
view.backgroundColor = .chiliRed.withAlphaComponent(0.5)
...

Perhaps even more interesting, though, are what doors that this new capability opens in terms of API design. As an example, in “Lightweight API design in Swift”, we took a look at the following API style, which involves extending a struct with static methods and properties that enables us to use it in a very “enum-like” way:

extension ImageFilter {
    static var dramatic: Self {
        ImageFilter(
            name: "Dramatic",
            icon: .drama,
            transforms: [
                .portrait(withZoomMultipler: 2.1),
                .contrastBoost,
                .grayScale(withBrightness: .dark)
            ]
        )
    }
}

When using Swift 5.4 (or later versions in the future), we might now add something like the following — which lets us easily combine two ImageFilter instances by concatenating their transforms:

extension ImageFilter {
    func combined(with filter: Self) -> Self {
        var newFilter = self
        newFilter.transforms += filter.transforms
        return newFilter
    }
}

With the above in place, we’ll now be able to work with dynamically combined filters using the same lightweight dot syntax as we could previously only use when referencing a single, pre-defined filter:

let filtered = image.withFilter(.dramatic.combined(with: .invert))

Pretty cool! I’m going to keep exploring what kind of APIs that this new language feature enables me to design, and I’ll of course keep sharing my learnings with you through future articles and podcast episodes.

Like always, let me know if you have any questions or feedback by reaching out via either Twitter or email. Thanks for reading!