什么是宏?
Swift Macros允许您在编译时间生成重复代码,从而使您的应用程序的代码库更易于阅读,并且不太繁琐。
有两种类型的宏:
- 自由主义宏代替代码中的其他内容。它们始终以主题标签(#)符号开始。
#caseDetection // Freestanding Macro
- 附加宏用作代码声明的属性。它们以@标志开始。
@CaseDetection // Attached Macro
创建新的宏
需要在依赖swift-syntax
库的特殊Package
中创建宏。
SwiftSntax是一组Swift库,用于解析,检查,生成和转换Swift源代码。这是GitHub repo。
创建一个新的宏转到New -> Package
并选择Swift Macro
。
键入宏的名称并创建Package
。
仅键入宏的实际名称,而无需宏后缀。例如。对于一个名为 addAsync 的宏,类型 addAsync 不是 addasyncmacro 。
宏软件包结构
在新创建的Package
中,您会找到一些自动生成的文件:
-
[Macro name].swift
您声明宏的签名 -
main.swift
您可以在其中测试宏的行为 -
[Macro name]Macro.swift
您在其中编写宏的实际实现 -
[Macro name]Tests.swift
您编写宏实现的测试
宏观角色
单个宏可以具有多个角色来定义其行为。
可用角色是:
@freestanding(表达式)
创建一个返回值的代码。
协议
ExpressionMacro
宣言
@freestanding(expression)
@freestanding(声明)
创建一个或多个声明。像struct
,function
,variable
或type
。
协议
DeclarationMacro
宣言
@freestanding (declaration, names: arbitrary)
@Attached(同行)
在应用于。
协议
PeerMacro
宣言
@attached(peer, names: overloaded)
@Attached(登录)
将访问者添加到属性。例如。将get
和set
添加到var
中。例如,swiftui中的@State
协议
AccessorMacro
宣言
@attached(accessor)
@Attached(成员Attribute)
在应用于。
的类型/扩展名中的声明中添加属性协议
MamberAttributeMacro
宣言
@attached(memberAttribute)
@Attached(会员)
添加了应用于应用的类型/扩展中的新声明。例如。在struct
中添加一个自定义的init()
。
协议
MemberMacro
宣言
@attached(member, names: named(init()))
@Attached(符合)
添加了协议的符合。
协议
ConformanceMacro
宣言
@attached(conformance)
构建宏
签名
在本指南中,我们将创建一个宏,该从completion
One创建async
功能。
要开始构建此宏,我们需要创建宏签名。
为此,请转到[Macro name].swift
文件并添加。
@attached(peer, names: overloaded)
public macro AddAsync() = #externalMacro(module: "AddAsyncMacros", type: "AddAsyncMacro")
在这里,您可以声明宏(AddAsync
)的名称,然后在#externalMacro
中指定它所在的模块和宏的类型。
执行
然后实现实际宏,转到[Macro name]Macro.swift
文件。
创建一个以宏名称命名的public struct
,并根据您在Macro signature
中指定的签名添加一致性。
因此,在新创建的struct
内部添加所需的方法。
public struct AddAsyncMacro: PeerMacro {
public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
// Implement macro
}
}
如果您的Macro signature
具有多个角色,则需要添加每个角色,例如:
// Signature
@attached(accessor)
@attached(memberAttribute)
@attached(member, names: named(init()))
public macro // ...
// Implementation
public struct MyMacro: AccessorMacro, MamberAttributeMacro, MemberMacro { }
要知道相应的协议,请参阅宏角色部分。
导出宏
在[Macro name]Macro.swift
文件中添加或编辑此代码中的新创建的宏。
@main
struct AddAsyncMacroPlugin: CompilerPlugin {
let providingMacros: [SwiftSyntaxMacros.Macro.Type] = [
AddAsyncMacro.self
]
}
扩展方法
扩展方法负责生成隐藏的代码。
在这里,代码 acro (declaration
)已连接成碎片(TokenSyntax
),并操纵以生成所需的附加代码。
要这样做,我们必须将declaration
施加到所需的语法中。
例如。如果可以将宏连接到struct
,我们将其施放到StructDeclSyntax
。
在这种情况下,宏只能连接到function
上
因此,在expansion
方法中添加:
guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else {
// TODO: Throw error
}
return []
现在,在我们继续之前,我们需要编写一个测试,以检查宏的实现是否生成了我们期望的代码。
因此,在[Macro name]Tests.swift
文件中添加:
func test_AddAsync() {
assertMacroExpansion(
"""
@AddAsync
func test(arg1: String, completion: (String?) -> Void) {
}
""",
expandedSource: """
func test(arg1: String, completion: (String?) -> Void) {
}
func test(arg1: String) async -> String? {
await withCheckedContinuation { continuation in
self.test(arg1: arg1) { object in
continuation.resume(returning: object)
}
}
}
""",
macros: testMacros
)
}
一旦到位,让我们在expansion
中的return []
添加一个断点并运行测试。
一旦我们碰到断点,请在调试控制台内运行po functionDecl
以获取这一长描述:
FunctionDeclSyntax
├─attributes: AttributeListSyntax
│ ╰─[0]: AttributeSyntax
│ ├─atSignToken: atSign
│ ╰─attributeName: SimpleTypeIdentifierSyntax
│ ╰─name: identifier("AddAsync")
├─funcKeyword: keyword(SwiftSyntax.Keyword.func)
├─identifier: identifier("test")
├─signature: FunctionSignatureSyntax
│ ╰─input: ParameterClauseSyntax
│ ├─leftParen: leftParen
│ ├─parameterList: FunctionParameterListSyntax
│ │ ├─[0]: FunctionParameterSyntax
│ │ │ ├─firstName: identifier("arg1")
│ │ │ ├─colon: colon
│ │ │ ├─type: SimpleTypeIdentifierSyntax
│ │ │ │ ╰─name: identifier("String")
│ │ │ ╰─trailingComma: comma
│ │ ╰─[1]: FunctionParameterSyntax
│ │ ├─firstName: identifier("completion")
│ │ ├─colon: colon
│ │ ╰─type: FunctionTypeSyntax
│ │ ├─leftParen: leftParen
│ │ ├─arguments: TupleTypeElementListSyntax
│ │ │ ╰─[0]: TupleTypeElementSyntax
│ │ │ ╰─type: OptionalTypeSyntax
│ │ │ ├─wrappedType: SimpleTypeIdentifierSyntax
│ │ │ │ ╰─name: identifier("String")
│ │ │ ╰─questionMark: postfixQuestionMark
│ │ ├─rightParen: rightParen
│ │ ╰─output: ReturnClauseSyntax
│ │ ├─arrow: arrow
│ │ ╰─returnType: SimpleTypeIdentifierSyntax
│ │ ╰─name: identifier("Void")
│ ╰─rightParen: rightParen
╰─body: CodeBlockSyntax
├─leftBrace: leftBrace
├─statements: CodeBlockItemListSyntax
╰─rightBrace: rightBrace
在这里您可以看到函数声明的每个组件。
现在,您可以选择所需的单独作品并使用它来创建宏观生成的代码。
检索第一个参数名称
例如,如果您需要检索函数的第一个参数名称,则将写下:
let signature = functionDecl.signature.as(FunctionSignatureSyntax.self)
let parameters = signature?.input.parameterList
let firstParameter = parameters?.first
let parameterName = firstParameter.firstName // -> arg1
这是一个很长的代码,只是为了检索一个字符串,如果您需要处理多个功能变体,它将更加复杂。
我认为苹果将来会改善这一点,但是现在让我们坚持下去。
完成实施
现在,让我们完成AddAsync
实现。
if let signature = functionDecl.signature.as(FunctionSignatureSyntax.self) {
let parameters = signature.input.parameterList
// 1.
if let completion = parameters.last,
let completionType = completion.type.as(FunctionTypeSyntax.self)?.arguments.first,
let remainPara = FunctionParameterListSyntax(parameters.removingLast()) {
// 2. returns "arg1: String"
let functionArgs = remainPara.map { parameter -> String in
guard let paraType = parameter.type.as(SimpleTypeIdentifierSyntax.self)?.name else { return "" }
return "\(parameter.firstName): \(paraType)"
}.joined(separator: ", ")
// 3. returns "arg1: arg1"
let calledArgs = remainPara.map { "\($0.firstName): \($0.firstName)" }.joined(separator: ", ")
// 4.
return [
"""
func \(functionDecl.identifier)(\(raw: functionArgs)) async -> \(completionType) {
await withCheckedContinuation { continuation in
self.\(functionDecl.identifier)(\(raw: calledArgs)) { object in
continuation.resume(returning: object)
}
}
}
"""
]
}
在这个代码块中,我们:
- 从函数签名中检索完成参数
- 解析函数参数,除了完成
- 创建传递到称为函数的参数
- 组成异步函数
显示自定义错误
宏使您可以向用户显示自定义错误。
例如,如果用户将宏放置在struct
上,但是该宏只能与functions
一起使用。
在这种情况下,您可以丢弃错误,并且将在Xcode中自动显示。
enum AsyncError: Error, CustomStringConvertible {
case onlyFunction
var description: String {
switch self {
case .onlyFunction:
return "@AddAsync can be attached only to functions."
}
}
}
// Inside `expansion` method.
guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else {
throw AsyncError.onlyFunction // <- Error thrown here
}
测试宏使用
测试AddAsync
宏的行为。
转到main.swift
文件并添加:
struct AsyncFunctions {
@AddAsync
func test(arg1: String, completion: (String) -> Void) {
}
}
func testing() async {
let result = await AsyncFunctions().test(arg1: "Blob")
}
您可以看到构建成功完成。
!警告自动完成可能不会显示生成的异步功能。
显示宏生成的代码
在代码中展开宏,并在宏中查看自动生成的代码,right click
,然后从菜单中选择Expand Macro
。
!
断点
宏产生的代码可以通过像往常一样添加断点来调试。
为此,请在宏观上right click
,然后从菜单中选择Expand Macro
。
然后在要调试的线路上添加一个断点。
结论
恭喜!您刚刚创建了第一个宏。
查看here完整的代码。
如您所见,到目前为止,宏实现可能是执行简单任务的很长的事件。
但是,一旦您缠绕着头,它们就会非常有用,并且可以节省很多样板代码。
宏仍处于beta状态,所以我认为苹果会在公开发售时会改善它们。
感谢您阅读