The @ and # symbols in Swift
Attributes, Macros, Property wrappers and more...
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.
Related articles
Here are some more articles that may interest you. Check them out!
The reentrancy problem
When a new feature is introduced in a programming language, we as developers are often quick to assume it will solve many of their challenges. The excitement to experiment with such features is natural, but it’s often accompanied by the realisation that every solution brings its own set of unique challenges. This was no different when actors were introduced.
Read moreDebugging memory leaks in Xcode
Ever got into a situation where you observed some strange behaviour with your app? Crashes, very bad performance or strange warnings in the console? Or simply you looked at the memory usage of your app and realised that it’s way too high for what your app should be doing? Yeah! Most likely you are dealing with a memory leak.
Read moreSimplify your code with @Entry
If you’ve ever tried to tap into the systems provided by SwiftUI, you are very likely to know, how quickly the boilerplate in your code can grow in size. The @Entry macro provides a solution to that
Read more