Lesson 98Advanced Topics
Swift Macros
Compile-Time Code Generation
Swift Macros (introduced in Swift 5.9) transform your code at compile time. They eliminate boilerplate while maintaining type safety.
Macro Types
# Freestanding
Standalone, produces value
#stringify, #warning
@ Attached
Attached to declarations
@Observable, @Model
Built-in Macros
#available- Check OS version#file, #line, #function- Debug info#warning, #error- Compile messages@Observable- Automatic observation@Model- SwiftData persistence
Attached Macro Types
- @attached(member) - Add properties/methods
- @attached(accessor) - Add getters/setters
- @attached(peer) - Add alongside declaration
- @attached(conformance) - Add protocols
Debugging Macros
In Xcode, right-click any macro usage and select "Expand Macro" to see the generated code. This helps understand what the macro produces.
main.swift
// SWIFT MACROS (Swift 5.9+)
// Generate code at compile time
// ===== WHAT ARE MACROS? =====
// Macros transform your source code at compile time
// They reduce boilerplate and add functionality
// ===== FREESTANDING MACROS (#) =====
// Produce a value or perform an action
// #stringify - Returns expression as string with value
let (value, code) = #stringify(2 + 3)
// value = 5, code = "2 + 3"
// #warning and #error - Compile-time messages
#warning("This needs to be fixed before release")
// #error("This code should never be used")
// #file, #line, #function - Debug info
func logLocation() {
print("Called from \(#file):\(#line) in \(#function)")
}
// ===== ATTACHED MACROS (@) =====
// Attached to declarations
// @Observable (Swift 5.9) - Automatic observation
@Observable
class Counter {
var count = 0
var name = "Counter"
}
// Expands to add observation tracking
// @Model (SwiftData) - Database model
// @Model
// class User {
// var name: String
// var age: Int
// }
// ===== COMMON BUILT-IN MACROS =====
// #available - Check API availability
if #available(iOS 17, *) {
print("iOS 17+ features available")
}
// #selector - Create Objective-C selectors
// let sel = #selector(viewDidLoad)
// #keyPath - Type-safe key paths
class Person {
@objc dynamic var name = ""
}
// let path = #keyPath(Person.name)
// #colorLiteral, #imageLiteral - Xcode literals
// let color = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)
// ===== CREATING CUSTOM MACROS =====
// Macros are defined in separate Swift packages
// 1. Freestanding Expression Macro
// @freestanding(expression)
// macro stringify<T>(_ value: T) -> (T, String)
// 2. Attached Member Macro
// @attached(member)
// macro Observable()
// 3. Attached Peer Macro
// @attached(peer)
// macro AddAsync()
// ===== MACRO IMPLEMENTATION =====
// Macros use SwiftSyntax to transform AST
// import SwiftSyntax
// import SwiftSyntaxMacros
//
// struct StringifyMacro: ExpressionMacro {
// static func expansion(
// of node: some FreestandingMacroExpansionSyntax,
// in context: some MacroExpansionContext
// ) -> ExprSyntax {
// // Transform syntax here
// }
// }
// ===== MACRO TYPES =====
// @freestanding(expression) - Produces a value
// @freestanding(declaration) - Creates declarations
// @attached(peer) - Adds alongside declaration
// @attached(accessor) - Adds get/set
// @attached(member) - Adds members to type
// @attached(memberAttribute) - Adds attributes to members
// @attached(conformance) - Adds protocol conformance
// ===== PRACTICAL EXAMPLES =====
// Auto-generate CodingKeys
// @CodingKeys
// struct User {
// var userName: String // -> "user_name"
// var emailAddress: String // -> "email_address"
// }
// Auto-implement Equatable
// @AutoEquatable
// struct Point {
// var x: Int
// var y: Int
// }
// Generate mock for testing
// @Mock
// protocol NetworkService {
// func fetch() async throws -> Data
// }
// ===== BENEFITS OF MACROS =====
// - Reduce boilerplate code
// - Compile-time safety
// - Better than runtime reflection
// - Clear expansion (can inspect in Xcode)
// ===== DEBUGGING MACROS =====
// Right-click macro usage in Xcode
// Select "Expand Macro" to see generated codeTry It Yourself!
Explore @Observable in a SwiftUI project and use "Expand Macro" to see the generated observation code!