The @ and # symbols in Swift

Attributes, Macros, Property wrappers and more...

by Krisztián
January 2, 2025

Swift offers a variety of special tools and keywords to extend the functionality of certain code entities and the compiler. Although we, as developers, are using many of these tools on a day to day basis, it can be really overwhelming what all these @ and # symbols mean and when should we use them. Let’s see some examples of each

The 'at' symbol

The @ symbol is usually used to attach functionality to existing properties, types or functions. There are several types of modifiers in swift, some are built into the language, other can be implemented by the programmers. Such modifiers can be attributes, property wrappers and so on

Attributes

Attributes are used to attach extra information to declarations or types. These modifiers will tell the compiler how to handle a certain piece of code. There are attributes with and without an argument list

Some examples of attributes

@discardableResults will tell the compiler that even though the function has a return value, it can be discarded so generating a warning is not necessary

@discardableResults
func removeItem() -> Item {}

// let _ = is not necessary to avoid warnings
removeItem()

@available signals the availability or deprecation of a certain entity in a specified version of OS or language

@available(iOS 16, *)
var myVariable: CustomType // Available only from iOS 16 up

Note that there are many different attributes that one can use, check out the official documentation to find out more about them https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/

Property wrappers

Property wrappers are a powerful tool for adding custom functionality to … properties. There are many built in property wrappers in the Apple frameworks, and you can also make your custom property wrappers by making use of the @propertyWrapper attribute (😏)

💡 To read more about how to create a custom property wrapper for adding advanced coddle functionality, check out this previous article of mine

Some examples of property wrappers are

@State from swiftui

@State var counter: Int = 0

… and @Query from SwiftData

@Query(sort: \Movie.title) var movies: [Movie]

Result builders

Result builders are the tool that make the creation very powerful and custom DSLs possible. They allow the building of a result set by lining up blocks of sub results one after the other. One example of such a DSL is SwiftUI itself. Let’s see what difference result builders make

// ❌: Without results builder: Invalid code, no return statements, the Text views are not handled
var myView: some View {
    if counter > 1 {
        Text("Counter greater than 1")
    }
    for i in 0...10 {
        Text("")
    }
}

// ✅: With results builder: Both the contents of if and the results of the for will be included in the final results
@ViewBuilder
var myView: some View {
    if counter > 1 {
        Text("Counter greater than 1")
    }
    for i in 0...10 {
        Text("")
    }
}

Attached macros

Attached macros modify the declaration they are attached. Some example of attached macros are @Observable which defines and implements the conformance of an object to the Observable protocol and @Model which transforms a type into a SwiftData model

@Observable
class AppState {
    var myState: Int = 0
}

@Model
class Item {
    var name: String = ""
}

Actor Isolation

The last item on our list is actor isolation. With the @ symbol, you can isolate a type, property or method to a global actor.

💡 To read more about a potential bug that many overlook when working with actors, check out this article

@MainActor
class AppState {
    var myState: Int = 0
}

The hash symbol

Opposed to the @ symbol, the hash symbol is usually used for tools that will be either replaced by some value or code at compilation or that alter how the compilation of a certain code snippet should be handled. Some examples of such tools include macros and compiler directives

Macros

Macros are a great way to transform your code at compilation time avoiding the need for writing repetitive code. They achieve this by adding or generating code to the codebase compile time

// Fetching data form a SwiftData context
let descriptor = FetchDescriptor(predicate: #Predicate { item in
    item.counter == 0
})

Note: To see what code the macro generates, you can right click on it and choose expand macro in Xcode

Compiler directives

Compiler directives are the tool that can be used to alter the compilation of a certain piece of code

💡 Check out this article to see how compiler directives can help you in making your code support multiple platforms

#if DEBUG
print("Documents dir: \(URL.documentsDirectory)")
#endif

In this example, the print call will only be compiled in debug builds and will be stripped from release builds

Conclusion

I hope this article helps you understand what tools swift offers its users a bit better. There are many more useful keywords that could not be fitted in this article, but the scope if it was not to include everything anyways, but to give a general understanding of the logic behind these tools.