Macros (Swift 5.9+)
Swift macros enable compile-time code generation and transformation.
Two Types of Macros
Freestanding Macros
Start with #, expand to code at the call site.
// Usage
let url = #URL("https://example.com")
// Expands to compile-time validated URLAttached Macros
Start with @, modify or add to declarations.
// Usage
@OptionSet
struct Permission {
private enum Options: Int {
case read = 1
case write = 2
case delete = 4
}
}
// Expands to add conformance, properties, initializersCommon Freestanding Macros
#URL
// Compile-time URL validation
let api = #URL("https://api.example.com/users")
// Error if URL is invalid#selector, #keyPath
// Type-safe selectors (UIKit)
button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
// Type-safe key paths
let name = user[keyPath: #keyPath(User.name)]Common Attached Macros
@OptionSet
Generates RawRepresentable conformance for option sets.
@OptionSet<UInt8>
struct ShippingOptions {
private enum Options: Int {
case nextDay
case priority
case gift
}
}
// Generated: init, contains, insert, remove, etc.
let options: ShippingOptions = [.nextDay, .gift]@Observable (SwiftUI)
Generates observation infrastructure for SwiftUI.
@Observable
class ViewModel {
var count = 0
}
// No need for @Published or ObservableObjectMacro Roles
Macros are defined with specific roles that determine what they can do:
| Role | What It Does | Example |
|---|---|---|
@freestanding(expression) |
Expands to an expression | #URL |
@attached(member) |
Adds members to a type | @Observable |
@attached(peer) |
Adds peer declarations | @Test generates async variant |
@attached(accessor) |
Adds getters/setters | @Observable property wrapper |
@attached(memberAttribute) |
Adds attributes to members | Apply @MainActor to all members |
@attached(conformance) |
Adds protocol conformances | @OptionSet adds OptionSet |
When to Use Macros
✅ Good Use Cases
- Eliminating boilerplate (OptionSet, Observable)
- Compile-time validation (#URL, #require)
- Code generation from declarations
- Type-safe wrappers
❌ Avoid For
- Runtime logic (use functions)
- Simple code reuse (use functions/protocols)
- Complex transformations (hard to debug)
- Anything achievable with protocols/generics
Macro Expansion
Macros expand at compile time. View expansions in Xcode:
- Right-click macro usage
- "Expand Macro" to see generated code
@OptionSet
struct Permissions {
private enum Options: Int {
case read, write
}
}
// Expand to see:
// - OptionSet conformance
// - Static properties
// - Initializers
// - Insert/remove methodsCreating Macros (High-Level)
Macros are separate Swift packages:
- Define the macro signature in your package
- Implement using SwiftSyntax in a macro target
- Test the expansion
- Use in your code
This is advanced - most developers only use macros, not create them.
Key Principles
- Additive only - Macros can't remove code
- Deterministic - Same input = same output
- Sandboxed - No file system, network, etc.
- Inspectable - Always viewable via "Expand Macro"