Attribute it to...

Attribute it to...

·

5 min read

(Reader beware: The use of @ doesn't constitute links to Twitter handles or Hashnode mentions 🤕)

Attributes

A Swift attribute attributes a declaration or type. Seriously. Merriam Webster defines an attribute as a quality, character, or characteristic ascribed to someone or something. Therefore, as such, we have something in our program say a declaration and we'd like to annotate it.

The Swift documentation has the following to say:

A Swift attribute provides more information about a declaration or type.

Swift attributes are very similar to annotations in other languages. It's a form of providing metadata to your programs. Attributes serve different purposes but in the end, they are just instructions for the compiler.

Syntax

An attribute is specified by prefixing it's name with an @ symbol e.g. @State. Some attributes accept arguments to provide more information on their functionality with the following syntax: @attribute(arguments).

Swift has 2 categories of attributes:

  • Type: this kind is applicable to types only
  • Declaration: applies to declarations only

Sample attributes

Let's look at a few attributes you might might be familiar with.

discardableResult

I think this was my first attribute to write (so I write it first 😎). This attribute is used to suppress the compiler's warning when we don't use the result of a function/method call that returns a value.

Say we have a method to start a poll that returns a boolean to indicate whether the polling process started successfully or not. Furthermore, for some particular invocations, we do not care about this return value. If we ignore the return value of the call, we get the following compile time warning:

Screen Shot 2020-07-02 at 11.11.08 ruc-inī.png

To inform the compiler we sometimes don't care about the returned result, we attribute the poll method with @discardableResult.

@discardableResult
func poll() -> Bool {
    ...
}

// call site
poll() // no compiler warning reported here

Alamofire, a popular networking library, heavily uses this attribute e.g. see its Request.swift file.

UIApplicationMain

The all familiar @UIApplicationMain is in all Swift iOS applications. It indicates that the attributed class is the Application's delegate.

@UIApplicationMain
class AppDelegate: UIResponder {
    ...
}

There's usually one application delegate thus the UIApplication.shared.delegate will return an instance of the above attributed class.

objc

Working with UIKit has definitely exposed you to this attribute. The @objc instructs the compiler to avail the attributed declaration for use in Objective-C code. Let's take a tap action for example.

let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
self.actionView.addGestureRecognizer(gestureRecognizer)

@objc private func tapAction(_ recognizer: UITapGestureRecognizer) {
    // useful action
}

In this example, the tapAction(_:) declaration is exposed to Objective-C for dispatch at runtime to respond to the tap action on self.actionView. The @objc attribute hints for this exposure.

testable

Ah. Testing. A test bundle is a target for a project. As such, the module under test needs to be imported for invocation in the test bundle. @testable is used to attribute an import declaration of the module's code for use in testing eg:

@testable import TheProject

"Why?", you may ask. Swift's documentation says the following about adding @testable attribute on an import declaration:

  • The imported module must be compiled with testing enabled
  • Entities in the imported module that are marked with the internal access-level modifier are imported as if they were declared with the public access-level modifier
  • Classes and class members that are marked with the internal or public access-level modifier are imported as if they were declared with the open access-level modifier

escaping

Swift supports closures - self-contained blocks of code passed around. A closure escapes a function when it's passed as an argument but invoked after the function returns. Lets create a Queue structure that enqueues tasks to be performed after some time:

final class Queue {

    private let queue: DispatchQueue = .main

    func enqueue(after deadline: DispatchTime, execute work: @escaping () -> Void) {
        self.queue.asyncAfter(deadline: deadline, execute: work)
    }

}

Our enqueue method schedules some work to be done after a given deadline using DispatchQueue. This request is delegated to a DispatchQueue using a method asyncAfter, that executes the given closure after a given deadline. The provided closure, work, therefore escapes (executes after the methods return) both our enqueue method and the asyncAfter method. Omitting the @escaping attribute in the enqueue(after:execute:) definition results in the following compiler error:

Screen Shot 2020-07-02 at 10.45.58 ruc-inī.png

As shown in the above code snippet, we need to attribute the closure declaration with @escaping. With this in place, our Queue class is ready to be used for scheduling work:

let queue = Queue()
queue.enqueue(after: .now() + 0.3) {
    print("Delayed work")
}

propertyWrapper

This is a recent addition to the Swift's attributes list that is applied to classes, structs or enumerations. It creates a custom attribute with the same name as the attributed type. A factory of attributes? It appears so. This makes it a powerful attribute and a useful one for that matter. For this reason (and the wide application in SwiftUI), I'll dedicate an article to discuss @propertyWrappers.

Conclusion

We've taken a look at some common attributes and their use cases. There're many more. Check out the Swift attribute documentation.

SwiftUI, the declarative UI framework, makes heavy use of Swift attributes such as @State, @Environment, @Published, @EnvironmentObject and many more making this an even more important concept to learn. Happy coding!

References

Did you find this article valuable?

Support Charles Muchene by becoming a sponsor. Any amount is appreciated!