如何用Swift宏为结构体添加成员并增强其初始化方法?
问题解答
1. 是否存在修改现有初始化器的宏角色?
目前Swift宏体系中,没有专门用于修改已有声明(比如现有初始化器)的attached宏角色。Attached成员宏(MemberMacro)仅能添加新的成员(属性、方法、初始化器等),无法修改或替换已存在的自定义初始化器。
2. 实现支持新成员的初始化方法的方案
既然无法修改原有初始化器,我们可以通过添加新的初始化器来实现需求,同时保留原有初始化器的可用性(因为新增的count属性带有默认值0)。
具体实现示例
目标效果
使用@Counted宏后,结构体将同时支持两种初始化方式:
- 原有方式:
Item(name: "Johnny Appleseed")(count默认值为0) - 新方式:
Item(name: "Johnny Appleseed", count: 5)
宏处理后的代码
struct Item { var name: String var count: Int = 0 init(name: String) { self.name = name } // 宏自动添加的初始化器 init(name: String, count: Int) { self.name = name self.count = count } }
宏的核心实现代码
通过MemberMacro角色,我们同时添加count属性和新的初始化器:
import SwiftSyntax import SwiftSyntaxMacros import SwiftSyntaxBuilder public enum CountedMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { // 1. 添加count属性 let countProperty = DeclSyntax( """ var count: Int = 0 """ ) // 2. 添加带count参数的初始化器 let customInitializer = DeclSyntax( """ init(name: String, count: Int) { self.name = name self.count = count } """ ) return [countProperty, customInitializer] } } // 注册宏 public struct CountedMacroPlugin: MacroPlugin { public static let providingMacros: [String: Macro.Type] = [ "Counted": CountedMacro.self ] }
通用化扩展(适配任意结构体)
如果希望@Counted宏能适配任意结构体,可通过SwiftSyntax遍历结构体的现有存储属性,动态生成包含所有原有属性和count的初始化器:
public enum CountedMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let structDecl = declaration as? StructDeclSyntax else { throw MacroError.onlyApplicableToStruct } // 获取结构体所有存储属性的名称和类型 let storedProperties = try structDecl.memberBlock.members.compactMap { member -> (name: String, type: TypeSyntax)? in guard let varDecl = member.decl as? VariableDeclSyntax, varDecl.bindingSpecifier.text == "var", let binding = varDecl.bindings.first, let idPattern = binding.pattern as? IdentifierPatternSyntax, let typeAnnotation = binding.typeAnnotation else { return nil } return (idPattern.identifier.text, typeAnnotation.type) } // 生成初始化器参数列表 let paramList = storedProperties.map { "\($0.name): \($0.type)" } .joined(separator: ", ") let paramWithCount = paramList.isEmpty ? "count: Int" : "\(paramList), count: Int" // 生成初始化器内部赋值语句 let assignStatements = storedProperties.map { "self.\($0.name) = \($0.name)" } .joined(separator: "\n") let fullBody = assignStatements.isEmpty ? "self.count = count" : "\(assignStatements)\nself.count = count" // 构建初始化器 let customInitializer = DeclSyntax( """ init(\(raw: paramWithCount)) { \(raw: fullBody) } """ ) // 添加count属性 let countProperty = DeclSyntax("var count: Int = 0") return [countProperty, customInitializer] } } enum MacroError: Error { case onlyApplicableToStruct }
这样,无论结构体原有哪些属性,@Counted宏都会自动添加count属性和包含所有原有属性+count的初始化器,同时原有自定义初始化器依然可用。
内容的提问来源于stack exchange,提问作者Curious Jorge
相关产品推荐
相关产品推荐

