Swift宏的实现

    上篇介绍了Swift宏的定义与生声明,本篇主要看看是Swift宏的具体实现。结合Swift中Codable协议,封装一个工具让类或者结构体自动实现Codable协议,并且添加一些协议中没有的功能。

关于Codable协议

    Codable很好,但是有一些缺陷:比如严格要求数据源,定义为String给了Int就抛异常、支持自定义CodingKey但是写法十分麻烦、缺字段的情况下不使用Optional会抛异常而不是使用缺省值等等。

基于以上情况,之前也写了一些Codable协议的补充,比如之前使用属性包装器增加了协议的默认值的提供具体地址https://github.com/duzhaoquan/DQTool.git

Swift Macro 的参考链接

  1. 【WWDC23】一文看懂 Swift Macro
  2. swift-macro-examples
  3. Swift AST Explorer
  4. CodableWrapper

实现目标:

Swift5.9之后新出了宏,通过宏可以更加优雅的封装Codable协议,增加新功能

  1. 支持缺省值,JSON缺少字段容错
  2. 支持 String Bool Number 等基本类型互转
  3. 驼峰大小写自动互转
  4. 自定义解析key
  5. 自定义解析规则 (Transformer)
  6. 方便的 Codable Class 子类

具体的实现

定义几个宏

  • @Codable
  • @CodableSubclass
  • @CodableKey(..)
  • @CodableNestedKey(..)
  • @CodableTransformer(..)

先简单的声明与实现

声明Codable和CodableKey宏。

// CodableWrapperMacros/CodableWrapper.swift@attached(member, names: named(init(from:)), named(encode(to:)))
@attached(conformance)
public macro Codable() = #externalMacro(module: "CodableWrapperMacros", type: "Codable")@attached(member)
public macro CodableKey(_ key: String ...) = #externalMacro(module: "CodableWrapperMacros", type: "CodableKey")

实现Codable和CodableKey宏。

// CodableWrapperMacros/Codable.swift
import SwiftSyntax
import SwiftSyntaxMacrospublic struct Codable: MemberMacro {public static func expansion(of _: AttributeSyntax,providingConformancesOf declaration: some DeclGroupSyntax,in _: some MacroExpansionContext) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)]{return []}public static func expansion(of node: SwiftSyntax.AttributeSyntax,providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax]{return []}
}
// CodableWrapperMacros/CodableKey.swift
import SwiftSyntax
import SwiftSyntaxMacrospublic struct CodableKey: ConformanceMacro, MemberMacro {public static func expansion(of node: SwiftSyntax.AttributeSyntax,providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax]{return []}
}

添加宏定义

​​// CodableWrapperMacros/Plugin.swift
import SwiftCompilerPlugin
import SwiftSyntaxMacros@main
struct CodableWrapperPlugin: CompilerPlugin {let providingMacros: [Macro.Type] = [Codable.self,CodableKey.self,]
}

 在这里,@Codable实现了两种宏,一种是一致性宏(Conformance Macro),另一种是成员宏(Member Macro)。

一些关于这些宏的说明:

  • @CodableCodable协议的宏名不会冲突,这样的命名一致性可以降低认知负担。
  • Conformance Macro用于自动让数据模型遵循Codable协议(如果尚未遵循)。
  • Member Macro用于添加init(from decoder: Decoder)func encode(to encoder: Encoder)这两个方法。在@attached(member, named(init(from:)), named(encode(to:)))中,必须声明新增方法的名称才是合法的。

实现自动遵循Codable协议

// CodableWrapperMacros/Codable.swiftpublic struct Codable: ConformanceMacro, MemberMacro {public static func expansion(of node: AttributeSyntax,providingConformancesOf declaration: some DeclGroupSyntax,in context: some MacroExpansionContext) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {return [("Codable", nil)]}public static func expansion(of node: SwiftSyntax.AttributeSyntax,providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax]{return []}
}

编译一下。右键@Codable -> Expand Macro查看扩写的代码,看起来还可以。

但如果BasicModel本身就遵循了Codable,编译就报错了。所以希望先检查数据模型是否遵循Codable协议,如果没有的话再遵循它,怎么办呢? 打开Swift AST Explorer 编写一个简单StructClass,可以看到整个AST,declaration: some DeclGroupSyntax对象根据模型是struct还是class分别对应了StructDeclClassDecl。补充上检查代码之后如下,增加了检查时否时class或者struct,否则抛出错误。代码如下

public static func expansion(of node: AttributeSyntax,providingConformancesOf declaration: some DeclGroupSyntax,in context: some MacroExpansionContext) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {var inheritedTypes: InheritedTypeListSyntax?if let declaration = declaration.as(StructDeclSyntax.self) {inheritedTypes = declaration.inheritanceClause?.inheritedTypeCollection} else if let declaration = declaration.as(ClassDeclSyntax.self) {inheritedTypes = declaration.inheritanceClause?.inheritedTypeCollection} else {throw ASTError("use @Codable in `struct` or `class`")}if let inheritedTypes = inheritedTypes,inheritedTypes.contains(where: { inherited in inherited.typeName.trimmedDescription == "Codable" }){return []}return [("Codable" as TypeSyntax, nil)]
}

实现 @Codable 功能

先定义个 ModelMemberPropertyContainerinit(from decoder: Decoder) 和 func encode(to encoder: Encoder) 的扩展都在里面实现。

public static func expansion(of node: SwiftSyntax.AttributeSyntax,providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax,in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax]
{let propertyContainer = try ModelMemberPropertyContainer(decl: declaration, context: context)let decoder = try propertyContainer.genDecoderInitializer(config: .init(isOverride: false))let encoder = try propertyContainer.genEncodeFunction(config: .init(isOverride: false))return [decoder, encoder]
}
// CodableWrapperMacros/ModelMemberPropertyContainer.swiftimport SwiftSyntax
import SwiftSyntaxMacrosstruct GenConfig {let isOverride: Bool
}struct ModelMemberPropertyContainer {let context: MacroExpansionContextfileprivate let decl: DeclGroupSyntaxinit(decl: DeclGroupSyntax, context: some MacroExpansionContext) throws {self.decl = declself.context = context}func genDecoderInitializer(config: GenConfig) throws -> DeclSyntax {return """init(from decoder: Decoder) throws {fatalError()}""" as DeclSyntax}func genEncodeFunction(config: GenConfig) throws -> DeclSyntax {return """func encode(to encoder: Encoder) throws {fatalError()}""" as DeclSyntax}
}

填充init(from decoder: Decoder) 

 需要得知属性名、@CodableKey的参数、@CodableNestedKey的参数、@CodableTransformer的参数、初始化表达式。获取memberProperties列表:

struct ModelMemberPropertyContainer {let context: MacroExpansionContextfileprivate let decl: DeclGroupSyntaxfileprivate var memberProperties: [ModelMemberProperty] = []init(decl: DeclGroupSyntax, context: some MacroExpansionContext) throws {self.decl = declself.context = contextmemberProperties = try fetchModelMemberProperties()}func fetchModelMemberProperties() throws -> [ModelMemberProperty] {let memberList = decl.memberBlock.memberslet memberProperties = try memberList.compactMap { member -> ModelMemberProperty? inguard let variable = member.decl.as(VariableDeclSyntax.self),variable.isStoredPropertyelse {return nil}// nameguard let name = variable.bindings.map(\.pattern).first(where: { $0.is(IdentifierPatternSyntax.self) })?.as(IdentifierPatternSyntax.self)?.identifier.text else {return nil}guard let type = variable.inferType else {throw ASTError("please declare property type: \(name)")}var mp = ModelMemberProperty(name: name, type: type)let attributes = variable.attributes// isOptionalmp.isOptional = variable.isOptionalType// CodableKeyif let customKeyMacro = attributes?.first(where: { element inelement.as(AttributeSyntax.self)?.attributeName.as(SimpleTypeIdentifierSyntax.self)?.description == "CodableKey"}) {mp.normalKeys = customKeyMacro.as(AttributeSyntax.self)?.argument?.as(TupleExprElementListSyntax.self)?.compactMap { $0.expression.description } ?? []}// CodableNestedKeyif let customKeyMacro = attributes?.first(where: { element inelement.as(AttributeSyntax.self)?.attributeName.as(SimpleTypeIdentifierSyntax.self)?.description == "CodableNestedKey"}) {mp.nestedKeys = customKeyMacro.as(AttributeSyntax.self)?.argument?.as(TupleExprElementListSyntax.self)?.compactMap { $0.expression.description } ?? []}// CodableTransformif let customKeyMacro = attributes?.first(where: { element inelement.as(AttributeSyntax.self)?.attributeName.as(SimpleTypeIdentifierSyntax.self)?.description == "CodableTransformer"}) {mp.transformerExpr = customKeyMacro.as(AttributeSyntax.self)?.argument?.as(TupleExprElementListSyntax.self)?.first?.expression.description}// initializerExprif let initializer = variable.bindings.compactMap(\.initializer).first {mp.initializerExpr = initializer.value.description}return mp}return memberProperties}
}

 完善genDecoderInitializer

    func genDecoderInitializer(config: GenConfig) throws -> DeclSyntax {// memberProperties: [ModelMemberProperty]let body = memberProperties.enumerated().map { idx, member inif let transformerExpr = member.transformerExpr {let transformerVar = context.makeUniqueName(String(idx))let tempJsonVar = member.namevar text = """let \(transformerVar) = \(transformerExpr)let \(tempJsonVar) = try? container.decode(type: type(of: \(transformerVar)).JSON.self, keys: [\(member.codingKeys.joined(separator: ", "))], nestedKeys: [\(member.nestedKeys.joined(separator: ", "))])"""if let initializerExpr = member.initializerExpr {text.append("""self.\(member.name) = \(transformerVar).transformFromJSON(\(tempJsonVar), fallback: \(initializerExpr))""")} else {text.append("""self.\(member.name) = \(transformerVar).transformFromJSON(\(tempJsonVar))""")}return text} else {let body = "container.decode(type: type(of: self.\(member.name)), keys: [\(member.codingKeys.joined(separator: ", "))], nestedKeys: [\(member.nestedKeys.joined(separator: ", "))])"if let initializerExpr = member.initializerExpr {return "self.\(member.name) = (try? \(body)) ?? (\(initializerExpr))"} else {return "self.\(member.name) = try \(body)"}}}.joined(separator: "\n")let decoder: DeclSyntax = """\(raw: attributesPrefix(option: [.public, .required]))init(from decoder: Decoder) throws {let container = try decoder.container(keyedBy: AnyCodingKey.self)\(raw: body)}"""return decoder}
  • let transformerVar = context.makeUniqueName(String(idx)) 需要生成一个局部transformer变量,为了防止变量名冲突使用了makeUniqueName生成唯一变量名

  • attributesPrefix(option: [.public, .required]) 根据 struct/class 是 open/public 生成正确的修饰。所有情况展开如下:

    open class Model: Codable {public required init(from decoder: Decoder) throws {}
    }public class Model: Codable {public required init(from decoder: Decoder) throws {}
    }class Model: Codable {required init(from decoder: Decoder) throws {}
    }public struct Model: Codable {public init(from decoder: Decoder) throws {}
    }struct Model: Codable {init(from decoder: Decoder) throws {}
    }
    

    填充func encode(to encoder: Encoder)

    func genEncodeFunction(config: GenConfig) throws -> DeclSyntax {let body = memberProperties.enumerated().map { idx, member inif let transformerExpr = member.transformerExpr {let transformerVar = context.makeUniqueName(String(idx))if member.isOptional {return """let \(transformerVar) = \(transformerExpr)if let \(member.name) = self.\(member.name), let value = \(transformerVar).transformToJSON(\(member.name)) {try container.encode(value: value, keys: [\(member.codingKeys.joined(separator: ", "))], nestedKeys: [\(member.nestedKeys.joined(separator: ", "))])}"""} else {return """let \(transformerVar) = \(transformerExpr)if let value = \(transformerVar).transformToJSON(self.\(member.name)) {try container.encode(value: value, keys: [\(member.codingKeys.joined(separator: ", "))], nestedKeys: [\(member.nestedKeys.joined(separator: ", "))])}"""}} else {return "try container.encode(value: self.\(member.name), keys: [\(member.codingKeys.joined(separator: ", "))], nestedKeys: [\(member.nestedKeys.joined(separator: ", "))])"}}.joined(separator: "\n")let encoder: DeclSyntax = """\(raw: attributesPrefix(option: [.open, .public]))func encode(to encoder: Encoder) throws {let container = encoder.container(keyedBy: AnyCodingKey.self)\(raw: body)}"""return encoder
    }
    

    @CodableKey @CodableNestedKey @CodableTransformer增加Diagnostics

这些宏是用作占位标记的,不需要实际扩展。但为了增加一些严谨性,比如在以下情况下希望增加错误提示:

@CodableKey("a")
struct StructWraning1 {}

实现也很简单抛出异常即可

public struct CodableKey: MemberMacro {public static func expansion(of node: AttributeSyntax, providingMembersOf _: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {throw ASTError("`\(self.self)` only use for `Property`")}
}

 这里也就印证了 @CodableKey 为什么不用 @attached(memberAttribute)(Member Attribute Macro) 而使用 @attached(member)(Member Macro) 的原因。如果不声明使用@attached(member),就不会执行MemberMacro协议的实现,在MemberMacro位置写上@CodableKey("a")也就不会报错。

实现@CodableSubclass,方便的Codable Class子类

先举例展示Codable Class子类的缺陷。编写一个简单的测试用例:是不是出乎意料,原因是编译器只给ClassModel添加了init(from decoder: Decoder)ClassSubmodel则没有。要解决问题还需要手动实现子类的Codable协议,十分不便:

@CodableSubclass就是解决这个问题,实现也很简单,在适时的位置super call,方法标记成override就可以了。

func genDecoderInitializer(config: GenConfig) throws -> DeclSyntax {...let decoder: DeclSyntax = """\(raw: attributesPrefix(option: [.public, .required]))init(from decoder: Decoder) throws {let container = try decoder.container(keyedBy: AnyCodingKey.self)\(raw: body)\(raw: config.isOverride ? "\ntry super.init(from: decoder)" : "")}"""
}func genEncodeFunction(config: GenConfig) throws -> DeclSyntax {...let encoder: DeclSyntax = """\(raw: attributesPrefix(option: [.open, .public]))\(raw: config.isOverride ? "override " : "")func encode(to encoder: Encoder) throws {\(raw: config.isOverride ? "try super.encode(to: encoder)\n" : "")let container = encoder.container(keyedBy: AnyCodingKey.self)\(raw: body)}"""
}

具体代码实现地址:GitHub - duzhaoquan/CodableTool

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/365777.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

141个图表,完美展示数据分类别关系!

本文介绍使用Python工具seaborn详细实现分类关系图表,包含8类图141个代码模版。 分类关系图表用于展示数字变量和一个或多个分类变量之间的关系,可以进一步分为:箱形图(box plot)、增强箱形图(enhanced bo…

DP:子数组问题

文章目录 引言子数组问题介绍动态规划的基本概念具体问题的解决方法动态规划解法:关于子数组问题的几个题1.最大子数组和2.环形子数组的最大和3.乘积最大子数组4.乘积为正数的最长子数组长度5.等差数列划分 总结 引言 介绍动态规划(DP)在解决…

Deep-LIBRA:一种用于可靠量化乳腺密度的人工智能方法,并在乳腺癌风险评估中进行了独立验证| 文献速递-深度学习自动化疾病检查

Title 题目 Deep-LIBRA: An artificial-intelligence method for robust quantification of breast density with independent validation in breast cancer risk assessment Deep-LIBRA:一种用于可靠量化乳腺密度的人工智能方法,并在乳腺癌风险评估中…

【内网渗透】从0到1的内网渗透基础概念笔记

目录 域 域的介绍 单域 父域和子域 域树 域森林 域名服务器 活动目录 活动目录介绍 域内权限 组 域本地组 全局组 通用组 总结 示例 A-G-DL-P策略 重要的域本地组 重要的全局组、通用组 安全域划分 域 域的介绍 Windows域是计算机网络的一种形式&#xf…

【机器学习】FFmpeg+Whisper:二阶段法视频理解(video-to-text)大模型实战

目录 一、引言 二、FFmpeg工具介绍 2.1 什么是FFmpeg 2.2 FFmpeg核心原理 2.3 FFmpeg使用示例 三、FFmpegWhisper二阶段法视频理解实战 3.1 FFmpeg安装 3.2 Whisper模型下载 3.3 FFmpeg抽取视频的音频 3.3.1 方案一:命令行方式使用ffmpeg 3.3.2 方案二&a…

[论文精读]Variational Graph Auto-Encoders

论文网址:[1611.07308] Variational Graph Auto-Encoders (arxiv.org) 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎…

Android 界面库 (二) 之 Data binding 详细介绍

1. 简介 回顾我们在前面文章《Android 界面库 (一) 之 View binding 简单使用》中学习的 View Binding,它旨在简化 View 与代码之间的绑定过程。它会在编译时期为每个 XML 布局文件生成相应的绑定类(Binding class),该类里包含了布局文件每个有 ID 的 Vi…

基于SpringBoot扶农助农政策管理系统设计和实现(源码+LW+调试文档+讲解等)

💗博主介绍:✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者,博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 🌟文末获取源码数据库🌟 感兴趣的可以先收藏起来,…

利用 Docker 简化 Nacos 部署:快速搭建 Nacos 服务

利用 Docker 简化 Nacos 部署:快速搭建 Nacos 服务 引言 在微服务架构中,服务注册与发现是确保服务间通信顺畅的关键组件。Nacos(Dynamic Naming and Configuration Service)作为阿里巴巴开源的一个服务发现和配置管理平台&…

【单片机毕业设计选题24039】-基于单片机的太阳能储能智能恒温外卖柜设计

系统功能: 以单片机为控制核心,综合运用传感器、物联网、太阳能等技术,设计一种基于单片机为控制核心的智能恒温外卖柜。它由恒温系统、无线模块、智能提醒系统、供电系统等组成,通过太阳能电池板独立供电,利用太阳能储能元件驱动…

Linux网络编程:套接字编程

1.Socket套接字编程 1.1.什么是socket套接字编程 Socket套接字编程 是一种基于网络层和传输层网络通信方式,它允许不同主机上的应用程序之间进行双向的数据通信。Socket是网络通信的基本构件,它提供了不同主机间的进程间通信端点的抽象。一个Socket就是…

免费开源的后端API服务-supabase安装和使用-简直是前端学习者福音

文章目录 它是什么安装和部署关于安装关于部署1、注册用户2、创建组织3、创建项目 创建数据库表(填充内容)填充数据库表 使用postman联调API 它是什么 一个开源免费的后端框架,firebase的替代品。可以简单理解类似于headless cms&#xff0c…

浅谈定时器之泊松随机定时器

浅谈定时器之泊松随机定时器 “泊松随机定时器”(Poisson Random Timer),它允许你基于泊松分布来随机化请求之间的延迟时间,这对于模拟具有随机到达率的事件特别有用,如用户访问网站或服务的请求。 泊松分布简介 泊松分布是一种统计与概率…

HarmonyOS开发探索:父子组件手势绑定问题处理

场景一:父子组件同时绑定手势的冲突处理 效果图 方案 在默认情况下,手势事件为非冒泡事件,当父子组件绑定相同的手势时,父子组件绑定的手势事件会发生竞争,最多只有一个组件的手势事件能够获得响应,默认子…

有哪些方法可以恢复ios15不小心删除的照片?

ios15怎么恢复删除的照片?在手机相册里意外删除了重要的照片?别担心!本文将为你介绍如何在iOS 15系统中恢复已删除的照片。无需专业知识,只需要按照以下步骤操作,你就能轻松找回宝贵的回忆。 一、从iCloud云端恢复删除…

Transformer动画讲解 - 工作原理

Transformer模型在多模态数据处理中扮演着重要角色,其能够高效、准确地处理包含不同类型(如图像、文本、音频、视频等)的多模态数据。 Transformer工作原理四部曲:Embedding(向量化)、Attention(注意力机制)、MLPs(多层感知机)和Unembedding(模型输出)。 阶段一:…

网上下载的PDF文件为何不能复制文字?该怎么办呢?

不知道大家有没有到过这种情况?在网上下载的PDF文件打开之后,发现选中文字之后无法复制。甚至其他功能也都无法使用,这是怎么回事?该怎么办? 首先,有可能PDF文件是扫描文件,是扫描文件的话&…

Gradle学习-4 创建二进制插件工程

二进制插件工程创建有两种方式: 创建独立的工程,调试的时候,需要手动发布成一个二进制插件jar包,给其他工程里面引用,进行功能测试。这种方式是比较麻烦的。创建buildSrc子工程,它是一个大工程中的子工程&…

云计算【第一阶段(19)】磁盘管理与文件系统 LVM与磁盘配额(二)

目录 一、LVM概述 1.1、LVM机制的基本概念 ​编辑 1.2、LVM的管理命令 1.3、lvm存储 两种机制 1.4、lvm应用实例 二、磁盘配额概述 2.1、设置磁盘配额 2.2.1、实现磁盘限额的条件 2.2.2、linux磁盘限额的特点 2.2.3、磁盘配额管理 一、LVM概述 1.1、LVM机制的基本概…

大模型ReAct:思考与工具协同完成复杂任务推理

ReAct: Synergizing Reasoning and Acting in Language Models Github:https://github.com/ysymyth/ReAct 一、动机 人类的认知通常具备一定的自我调节(self-regulation)和策略制定(strategization)的能力&#xff0…