Swift Macro中Peer与Member Macro的区别及ObjC兼容问题咨询
Swift Macro: Peer vs Member Macro, Objective-C Compatibility, and Bridging
1. Peer Macro 与 Member Macro 的核心区别
- 作用目标与位置
- Member Macro:作用于类/结构体/枚举等类型本身,用于给该类型添加新的成员(属性、方法、下标等),属于类型的"内部扩展"。比如给
TestModel类标注的@ObjCConvertMacro,生成的convertCaseFromOptionalIntObjc是类的顶层成员。 - Peer Macro:作用于单个声明元素(属性、方法等),用于给该元素添加同级的新声明。比如给
convertCaseFromOptionalInt属性标注的@ObjCConvertMacro,生成的convertCaseFromOptionalIntObjc是和原属性同级的成员。
- Member Macro:作用于类/结构体/枚举等类型本身,用于给该类型添加新的成员(属性、方法、下标等),属于类型的"内部扩展"。比如给
- 语法应用场景
- Member Macro适合批量给类添加统一规则的成员,比如给类中所有可选属性生成OC兼容的桥接属性。
- Peer Macro适合针对单个特定元素生成配套的同级声明,比如只为某一个属性生成对应的OC桥接属性。
2. 为何Member Macro可在Objective-C正常工作,Peer Macro却不行?
问题根源在于Swift与Objective-C的桥接元数据生成机制:
- 使用Member Macro时,生成的带
@objc标记的成员是作为类的顶层成员被编译器处理的,编译器会自动把这些成员纳入到类的Objective-C接口头文件(.h)中,OC代码能正常识别。 - 而Peer Macro生成的同级成员,即便带有
@objc标记,Swift编译器在生成OC桥接元数据时,默认不会将属性上的Peer Macro扩展内容纳入到类的OC可见接口里——简单说就是编译器没把Peer Macro生成的成员同步给Objective-C,导致OC代码看不到这些属性。
3. 如何通过Peer Macro实现Swift到Objective-C的桥接?
要让Peer Macro生成的成员被Objective-C识别,需要满足两个核心条件:
关键实现要点
- 确保Macro生成的成员必须带有
@objc和dynamic标记(dynamic保证属性能被OC运行时正常访问)。 - 生成符合OC桥接要求的类型转换代码(比如
Int?与NSNumber?的互转)。 - 宿主类必须继承自
NSObject(这是Swift代码桥接到OC的基础前提)。
修正后的Peer Macro代码示例
假设你的@ObjCConvertMacro是Peer Macro,生成的代码需明确包含@objc dynamic:
// Peer Macro 应用示例 class TestModel: NSObject, Codable { @ObjCConvertMacro var convertCaseFromOptionalInt: Int? // Macro 自动生成的代码 @objc dynamic var convertCaseFromOptionalIntObjc: NSNumber? { get { convertCaseFromOptionalInt as? NSNumber } set { convertCaseFromOptionalInt = newValue?.intValue } } }
Macro实现的关键提示
编写Peer Macro的扩展逻辑时,要确保生成的声明节点包含@objc和dynamic属性:
// 简化的Peer Macro实现示例 enum ObjCConvertMacro: PeerMacro { static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let varDecl = declaration.as(VariableDeclSyntax.self), let varName = varDecl.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text else { return [] } // 生成带@objc dynamic的桥接属性 let objcVarName = varName + "Objc" return [ """ @objc dynamic var \(raw: objcVarName): NSNumber? { get { \(raw: varName) as? NSNumber } set { \(raw: varName) = newValue?.intValue } } """ ] } }
这样生成的属性会被编译器正确纳入OC桥接元数据,OC代码就能通过testModel.convertCaseFromOptionalIntObjc访问该属性了。
内容的提问来源于stack exchange,提问作者wargi
相关产品推荐
相关产品推荐

