Understanding Swift Macros in Swift 5.9 and Their Applications
Swift 5.9’s new macro system lets developers generate type‑safe code at compile time via external plug‑ins, using role‑based declarations such as @attached and @freestanding to create properties, extensions, or warnings, thereby reducing repetitive boiler‑plate and improving safety in large‑scale architectures like MVVP modules and exposure frameworks.
Swift Macro was introduced in Swift 5.9 and Xcode 15, providing a way to reduce repetitive code by generating code at compile time.
Unlike Objective‑C #define macros, Swift macros are expanded during compilation, allowing type‑safe checks and better diagnostics.
Swift macros are mainly external macros handled by a Compiler Plug‑in. A macro consists of a role declaration (e.g., @attached(member), @freestanding(expression)) and a method declaration marked with the macro and #externalMacro keywords.
#define SQUARE(x) x * x
// ❌ 展开后逻辑不符
int a = 5;
int result = SQUARE(a + 1); // 结果为 5 + 1 * 5 + 1 = 11
// ❌ 缺少类型检查
int result = SQUARE("BiliBili") // 结果为 BiliBili * BiliBiliExample: a DefaultAge macro that generates an Int32 age property.
public macro DefaultAge(_ age: Int32) @DefaultAge(12.0)
class Test {} class Test {
var age: Int32 = ❌ // Cannot convert value of type 'Double' to specified type 'Int32'
} public macro DefaultAge(_ age: Int32) = #externalMacro(
module: "宏实现的模块",
type: "宏实现的类型"
)During expansion the compiler builds an AST, passes it to the plug‑in, which returns a new AST that is merged into the original source.
ClassDecl
-- MemberBlock
-- MemberBlockItemList
-- MemberBlockItem
-- name (String) == "BiliBili" @DefaultAge(12)
class Test {
var name: String = "BiliBili"
} ClassDecl
-- MemberBlock
-- MemberBlockItemList
-- name (String) == "BiliBili"
-- MemberBlockItem
-- age (Int) == 12Macro role definitions:
@attached(member, names: arbitrary) @attached(peer, names: overloaded) @freestanding(expression) @freestanding(declaration)Freestanding expression macro example that logs function info:
@freestanding(expression)
public macro function()
func logInfo(function: String = #function, lineNum: UInt = #line) {}Freestanding declaration macro that creates a warning:
@freestanding(declaration)
public macro warning(_ message: String) -> ()
#warning("插入一句警告")Attached macros can generate members, extensions, or accessors. Example of a member macro that creates an age property:
public macro DefaultAge(_ age: Int32) = #externalMacro(
module: "Macro",
type: "DefaultAgeMacro"
)Application scenario 1 – modular code generation for BiliBili’s MVVP modules:
protocol BiliModule: AnyObject {
associatedtype ModuleView: BiliModuleView
associatedtype ModulePresenter: BiliModulePresenter
static var moduleIdentifier: BiliModuleIdentifier { get }
var view: ModuleView? { get set }
var presenter: ModulePresenter { get }
var moduleSize: CGSize { get }
} class TabModule: BiliModule {
typealias ModuleView = TabView
typealias ModulePresenter = TabPresenter
static var moduleIdentifier: BiliModuleIdentifier { .TAB }
var view: TabView?
var presenter: TabPresenter
required init(context: BiliContext, data: BiliModuleInfo) {
self.presenter = TabPresenter(context: context, data: data)
}
} @attached(member, names: arbitrary)
@attached(extension, conformances: BiliModule)
public macro BiliModuleDefine
(_ presenter: P, _ view: V, _ type: BiliModuleIdentifier)Application scenario 2 – exposure system macros that automatically add container, target and region logic.
class ExposureCollectionView: UICollectionView, BiliExposureContainer {
var exposureCheckFlag: Int32 = 0
var exposureContext: BiliExposureContext = BiliExposureContext()
public func visibleExposureTargets() -> [UIView]? { return self.visibleCells }
} @ExposureContainer
class ExposureCollectionView: UICollectionView {} @ExposureTarget(100, true)
class ExposeData: NSObject {} @ExposureRegion
class VipCollectionViewCell: UICollectionViewCell, BiliExposureRegion {
@ExposureTargetMember
var exposureData: ExposureData?
func exposureTarget() -> BiliExposureTarget? { return exposureData }
}These examples demonstrate how Swift macros can encapsulate repetitive patterns, enforce compile‑time safety, and streamline large‑scale code bases.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.