前言

没有宏之前的痛点

在 Swift 5.9 之前,我们经常遇到这些问题:

// 痛点 1:大量重复的样板代码
struct User: Codable, Equatable, Hashable {

let id: Int
let name: String
let email: String
// 手动实现 CodingKeys(如果属性名不匹配)
enum CodingKeys: String, CodingKey {

case id = "user_id"
case name = "user_name"
case email = "user_email"
}
// 有时需要手动实现 Equatable
static func == (lhs: User, rhs: User) -> Bool {

lhs.id == rhs.id && lhs.name == rhs.name && lhs.email == rhs.email
}
// 手动实现 Hashable
func hash(into hasher: inout Hasher) {

hasher.combine(id)
hasher.combine(name)
hasher.combine(email)
}
}
// 痛点 2:调试信息获取困难
func debugLog(_ message: String, file: String = #file, line: Int = #line, function: String = #function) {

print("[\(file):\(line)] \(function) - \(message)")
}
// 痛点 3:缺乏编译时代码生成能力
// 只能通过运行时反射或代码生成工具(如 Sourcery)

Swift 宏带来的改变

// ✨ 使用宏后的优雅代码
// 1. 一行代码搞定
@Observable
class User {

var id: Int
var name: String
var email: String
}
// 2. 智能调试
#warning("TODO: Implement this feature")
let value = #unwrap(optionalValue, "Value should not be nil")
// 3. 编译时代码生成
@CodingKeys(using: .snakeCase)
struct User: Codable {

let userId: Int      // 自动映射到 "user_id"
let userName: String // 自动映射到 "user_name"
}

基础概念

什么是 Swift 宏?

Swift 宏是一种编译时代码转换机制,它在编译阶段读取源代码,并生成新的代码插入到程序中。

┌─────────────────────────────────────────────────────────────────┐
│                        编译流程                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │
│  │ 源代码   │ -> │ 宏展开   │ -> │ 类型检查 │ -> │ 代码生成 │  │
│  │ @MyMacro │    │ 生成代码 │    │ 验证正确 │    │ 最终产物 │  │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘  │
│                                                                  │
│       ↑               │                                          │
│       │               ↓                                          │
│  ┌─────────────────────────────────────┐                        │
│  │        宏实现(Swift Package)        │                        │
│  │  - 解析语法树                         │                        │
│  │  - 生成新的语法节点                   │                        │
│  │  - 返回给编译器                       │                        │
│  └─────────────────────────────────────┘                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

宏 vs 其他元编程方式

方式时机类型安全可调试性示例
Swift 宏编译时✅ 完全✅ 可展开查看@Observable
泛型编译时✅ 完全Array<T>
协议扩展编译时✅ 完全Collection.map()
运行时反射运行时❌ 有限❌ 困难Mirror
Sourcery构建时❌ 外部工具⚠️ 生成文件代码生成
C 宏预处理❌ 文本替换❌ 难以调试#define

宏的核心优势

// 1. 类型安全:宏生成的代码经过完整的类型检查
@Observable
class Counter {

var count = 0  // 宏确保正确实现 Observable 协议
}
// 2. 编译时验证:错误在编译时捕获
@MyMacro
struct Invalid {

// 如果结构不符合宏的要求,编译时报错
}
// 3. IDE 支持:可以展开查看生成的代码
// 在 Xcode 中右键 -> Expand Macro
@Observable  // <- 右键可以看到展开后的完整代码
class Model {

var value = 0
}
// 4. 可组合:多个宏可以一起使用
@Observable
@MainActor
class ViewModel {

var data: [Item] = []
}

宏的类型

Swift 提供了 7 种宏角色(Macro Roles),每种适用于不同场景:

1. 独立表达式宏(Freestanding Expression Macro)

# 开头,生成一个表达式值。

// 定义
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(...)
  // 使用
  let (result, str) = #stringify(1 + 2)
  // result = 3
  // str = "1 + 2"
  // 展开后等价于:
  let (result, str) = (1 + 2, "1 + 2")

2. 独立声明宏(Freestanding Declaration Macro)

# 开头,生成一个或多个声明。

// 定义
@freestanding(declaration, names: named(CodingKeys))
public macro CodingKeys() = #externalMacro(...)
// 使用
struct User: Codable {

let firstName: String
let lastName: String
#CodingKeys()  // 生成 CodingKeys 枚举
}
// 展开后:
struct User: Codable {

let firstName: String
let lastName: String
enum CodingKeys: String, CodingKey {

case firstName = "first_name"
case lastName = "last_name"
}
}

3. 附加成员宏(Attached Member Macro)

为类型添加新成员。

// 定义
@attached(member, names: named(id), named(createdAt))
public macro Identifiable() = #externalMacro(...)
// 使用
@Identifiable
struct Article {

var title: String
var content: String
}
// 展开后:
struct Article {

var title: String
var content: String
// 宏添加的成员
let id: UUID = UUID()
let createdAt: Date = Date()
}

4. 附加访问器宏(Attached Accessor Macro)

为属性添加 get/set 等访问器。

// 定义
@attached(accessor)
public macro UserDefault<T>(_ key: String, defaultValue: T) = #externalMacro(...)
  // 使用
  struct Settings {
  
  @UserDefault("theme", defaultValue: "light")
  var theme: String
  }
  // 展开后:
  struct Settings {
  
  var theme: String {
  
  get {
  
  UserDefaults.standard.string(forKey: "theme") ?? "light"
  }
  set {
  
  UserDefaults.standard.set(newValue, forKey: "theme")
  }
  }
  }

5. 附加成员属性宏(Attached MemberAttribute Macro)

为类型的所有成员添加属性。

// 定义
@attached(memberAttribute)
public macro PublishedProperties() = #externalMacro(...)
// 使用
@PublishedProperties
class ViewModel: ObservableObject {

var name: String = ""
var age: Int = 0
}
// 展开后:
class ViewModel: ObservableObject {

@Published var name: String = ""
@Published var age: Int = 0
}

6. 附加对等宏(Attached Peer Macro)

在同级别添加新声明。

// 定义
@attached(peer, names: overloaded)
public macro AddAsync() = #externalMacro(...)
// 使用
struct API {

@AddAsync
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
  
  // 同步实现
  }
  }
  // 展开后:
  struct API {
  
  func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    
    // 同步实现
    }
    // 宏添加的异步版本
    func fetchData() async throws -> Data {
    
    try await withCheckedThrowingContinuation {
     continuation in
    fetchData {
     result in
    continuation.resume(with: result)
    }
    }
    }
    }

7. 附加扩展宏(Attached Extension Macro)

为类型添加扩展。

// 定义
@attached(extension, conformances: Equatable, Hashable, names: named(==), named(hash))
public macro AutoEquatable() = #externalMacro(...)
// 使用
@AutoEquatable
struct Point {

var x: Double
var y: Double
}
// 展开后:
struct Point {

var x: Double
var y: Double
}
extension Point: Equatable, Hashable {

static func == (lhs: Point, rhs: Point) -> Bool {

lhs.x == rhs.x && lhs.y == rhs.y
}
func hash(into hasher: inout Hasher) {

hasher.combine(x)
hasher.combine(y)
}
}

宏角色组合

一个宏可以同时具有多个角色:

// @Observable 就是一个多角色宏
@attached(member, names: ...)
@attached(memberAttribute)
@attached(extension, conformances: Observable)
public macro Observable() = #externalMacro(...)

内置宏

Swift 标准库内置宏

1. #warning 和 #error
// 编译时警告
#warning("TODO: Optimize this algorithm")
// 编译时错误
#if DEBUG
let apiKey = "debug-key"
#else
#error("Production API key not configured")
#endif
// 条件警告
func deprecatedFunction() {

#warning("This function will be removed in v2.0")
}
2. #file, #line, #function, #column
func log(
_ message: String,
file: String = #file,
line: Int = #line,
column: Int = #column,
function: String = #function
) {

print("[\(file):\(line):\(column)] \(function): \(message)")
}
// 调用
log("Something happened")
// 输出: [/path/to/File.swift:42:5] myFunction(): Something happened
// Swift 5.8+ 新增
#fileID    // "ModuleName/FileName.swift"
#filePath  // 完整路径(调试用)
3. #selector 和 #keyPath
class MyClass: NSObject {

@objc var name: String = ""
@objc func handleTap()