@warn_unqualified_access

Credits to Matt Young for the tip!

In recent Swift releases, we've seen a surge of new attributes such as @unknown, @propertyWrapper, and @main, to name a few:
while all of these are new and exciting on their own, in this article, let's focus on a lesser-known, older, but equally helpful attribute, @warn_unqualified_access.

Introduction

Quietly introduced in Swift 2, @warn_unqualified_access helps us remove ambiguity when calling top-level functions and/or static/instance methods.

Imagine building a new app that has a top-level function, doSomething:

@main
struct FiveStarsApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

func doSomething() {
  print("top-level function")
  // ...
}

This example uses SwiftUI, but @warn_unqualified_access can be used everywhere, as it's a Swift feature.

Now, one of our views also declares and uses a doSomething method with the same signature:

struct ContentView: View {
  var body: some View {
    Button(action: doSomething) {
      Text("Tap me")
    }
  }

  func doSomething() {
    print("instance method")
    // ...
  }
}

When the button is tapped, doSomething is called: but which one? Previous experiences in the same scenario might help us here, and we probably know that the instance method has priority over the top-level function.

This was a quick and simple example, but imagine working on a team and having a view with several more declarations. Maybe a team member wasn't aware of the instance method and meant to call the top-level function instead: how can we prevent such scenarios?

This is where @warn_unqualified_access comes in: adding this attribute to any method declaration will trigger a warning at the call site when its qualifier is not specified (a.k.a. when the call doesn't specify either the intended instance, class, or module).

Going back to our example, let's add our new attribute to the function declaration:

struct ContentView: View {
  var body: some View {
    Button(action: doSomething) {
      Text("Tap me")
    }
  }

  @warn_unqualified_access
  func doSomething() {
    print("instance method")
    // ...
  }
}

Building the project will now trigger a warning on the button's action. It reads: Use of 'doSomething' treated as a reference to instance method in struct 'ContentView', and then proposes two actions:

  1. Use 'self.' to silence this warning
  2. Use 'FiveStars.' to reference the global function

Even if a team member weren't aware of the instance method at first, this warning would make sure they become aware of it and provide the two possible solutions to fix the warning.

A static method example

For completeness's sake, the following is an example where, instead of an instance method, a class method is declared with the same attribute (note how everything is now static):

class FSClass {
  static func main() {
    doSomething() // this will trigger a warning
  }

  @warn_unqualified_access
  static func doSomething() {
    print("class method")
    /// ...
  }
}

Origin

The reason for this attribute goes back to macOS's NSView, where the Objective-C NSView's print: instance method was translated to print(_:) in Swift:
since Swift.print is a top-level function, when used within a NSView subclass, it's lower priority than NSView's one, meaning that calling print(..) would not print on the debugger, but instead, it triggered the print dialog, yikes!

This was such an annoying issue that in Xcode 9 the NSView's method Swift translation has been renamed to printView(_:), ending the ambiguity once for all.

Real example: min/max

Swift provides min and max, two generic top-level functions that take in two Comparable elements.

Pretty much every Swift's Sequence variation also provides min max methods: to avoid any confusion, all of them have been marked with @warn_unqualified_access.

Adding @warn_unqualified_access here helps to avoid disambiguation when we invoke a min/max method within any of our own sequences.

SwiftUI

In Impossible SwiftUI views we've seen how easy it is to create views that compile fine but reliably crash our app:
while preventing such scenarios might be tricky without changing SwiftUI's declarations, we can start from our View extensions.

Imagine to have a design system, where all Text instances will use a given Font and foreground Color, since this is very common, we've decided to create a View extension:

extension View {
  func textStyle(
    _ font: Font? = nil,
    foregroundColor: Color = .black
  ) -> some View {
    self
      .font(font)
      .foregroundColor(foregroundColor)
  }
}

However, this extension won't stop us to declare views like the following:

struct ContentView: View {
  var body: some View {
    Text("Hello, world!")
    textStyle() // <-- missing the "."
  }
}

struct ContentView: View {
  var body: some View {
    textStyle() // <-- missing the actual `Text` component
  }
}

Which will end up in a guaranteed crash.

If we now add @warn_unqualified_access to our textStyle method declaration, this changes, as we now get a warning at compile time:

struct ContentView: View {
  var body: some View {
    Text("Hello, world!")
    textStyle() // Warning: Use of 'textStyle' treated as a reference to instance method in protocol 'View'
  }
}

struct ContentView: View {
  var body: some View {
    textStyle() // Warning: Use of 'textStyle' treated as a reference to instance method in protocol 'View'
  }
}

We can still write self.textStyle(), inhibiting the warning and still ending up in a crash, but most likely, this attribute will help us avoid writing bad declarations by mistake.

Conclusions

In this article, we've explored @warn_unqualified_access, a little known Swift attribute that can help us write better and clearer Swift code.

Do you use @warn_unqualified_access? Have you seen any other cool examples? Please let me know!

Thank you for reading, and stay tuned for more articles!

⭑⭑⭑⭑⭑

Further Reading

Explore Swift

Browse all

Explore SwiftUI

Browse all