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.mzph.cn/bicheng/38065.shtml

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

相关文章

yaklang window安装 vscode运行得到“hello world”

资源来源:旅程伊始:Yak 语言环境安装与搭建环境 | Yak Program Language 安装yak语言非常简单,管理员权限打开命令行运行以下命令: powershell (new-object System.Net.WebClient).DownloadFile(https://yaklang.oss-cn-beijing…

1085 PAT单位排行(测试点5)

solution 测试点5:总分是在每个学生加权后再取整,所以用来存学生分数的变量要用浮点型学校排序: 若成绩不同,则按成绩降序若成绩相同,人数不同,则按成绩升序若成绩和人数都相同,则按单位名升序…

理解GPT2:无监督学习的多任务语言模型

目录 一、背景与动机 二、卖点与创新 三、几个问题 四、具体是如何做的 1、更多、优质的数据,更大的模型 2、大数据量,大模型使得zero-shot成为可能 3、使用prompt做下游任务 五、一些资料 一、背景与动机 基于 Transformer 解码器的 GPT-1 证明…

NAS教程丨铁威马如何登录 SSH终端?

适用型号: 所有TNAS 型号 如您有特殊操作需要通过 SSH 终端登录 TNAS,请参照以下指引: (注意: 关于以下操作步骤中的"cd /"的指令,其作用是使当前 SSH/Telnet 连接的位置切换到根目录,以免造成对卷的占用.请不要遗漏它.) Windows…

数据分析的线上云端数据库搭建及Excel和Tableau连接

数据分析的线上云端数据库搭建及Excel和Tableau连接 SQL基础知识 线上SQL训练: SQlZOO: https://www.sqlzoo.net/wiki/SQL_Tutorial 牛客网SQL真题:https://www.nowcoder.com/ta/sql select,from,where, order by, limit, group by, having, substr(),…

【SpringBoot】SpringBoot使用mail实现登录邮箱验证

📝个人主页:哈__ 期待您的关注 目录 一、前期准备 1 开启邮箱服务 2 SpringBoot导入依赖 3 创建application.yml配置文件 4 创建数据库文件 5 配置redis服务 二、验证邮件发送功能 三、注册功能实现邮箱验证 1 创建User实体类 2 创建UserPa…

DEBOPIE框架:打造最好的ChatGPT交易机器人

本文介绍了如何利用 DEBOPIE 框架并基于 ChatGPT 创建高效交易机器人,并强调了在使用 AI 辅助交易时需要注意的限制以及操作步骤。原文: Build the Best ChatGPT Trading Bots with my “DEBOPIE” Framework 如今有大量文章介绍如何通过 ChatGPT 帮助决定如何以及在…

仓库管理系统12--供应商设置

1、添加供应商窗体 2、布局控件UI <UserControl x:Class"West.StoreMgr.View.SupplierView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc"http://…

为什么前端传了token,后端一直获取不到?一直报跨域错误?

这是我的前端代码 这是我的后端拦截器 那就需要了解一下 预检请求 对于非简单请求&#xff08;如PUT、DELETE或包含自定义HTTP头的请求&#xff09;&#xff0c;浏览器会先发送一个OPTIONS请求到目标服务器&#xff0c;询问是否允许该跨域请求。这个过程称为预检请求。 当opt…

【爬虫实战】今日头条-关键词搜索-快速整理出1w条数据

快速整理头条关键词数据工具&#xff0c;学习效率妥妥翻倍&#xff01;&#xff01;&#xff01;本案例源码仅供学习参考&#xff01; 项目功能简介&#xff1a; 1.可视化式配置&#xff1b; 2.任意关键词&#xff1b; 3.自动翻页&#xff1b; 4.支持指定最大翻页页码&…

IP地址网络号:解读其构成与重要性

在数字化时代&#xff0c;IP地址已成为我们网络生活不可或缺的一部分。每个设备在网络中都有一个独特的IP地址&#xff0c;这个地址由网络号和主机号组成&#xff0c;它们共同构成了我们的网络身份。其中&#xff0c;网络号的作用尤为重要&#xff0c;它决定了设备所连接的网络…

【算法专题--栈】用栈实现队列 -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐双栈 模拟 队列 &#x1f95d;栈 和 队列 的特性 &#x1f34d;具体思路 &#x1f34d;案例图解 四、总结与提炼 五、共勉 一、前言 用栈实现队列 这道题&#xff0c;可以说是--栈专题--&#xff0c;最经典的一道题&…

管理上的一些思考

1 前言 管理可分为自我管理、平级管理、向下管理和向上管理。 顾名思义&#xff0c;自我管理就是对自己工作、生活等各方面的规划和执行&#xff0c;不涉及与其他人互动、配合等。我们设定人生目标、年度计划、月计划等&#xff0c;都可以认为是自我管理。《增广贤文》有段很…

静态时序分析:ideal_clock、propagated_clock以及generated_clock的关系及其延迟计算规则(二)

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 生成时钟 上一节中&#xff0c;我们讨论了理想时钟和传播时钟的创建和使用&#xff0c;本节将讨论生成时钟及其与理想时钟和传播时钟的关系。 图1所示的是一个简…

Mysql基本知识点

1.数据库的基本操作 显示当前的数据库 show databases;创建一个数据库 直接创建数据库 create database 数据库名字;如果系统没有 test2 的数据库&#xff0c;则创建一个名叫 test2 的数据库&#xff0c;如果有则不创建 create database if not exists test2;如果系统没有 db…

【网络】计算机网络-基本知识

目录 概念计算机网络功能计算机网络的组成计算机网络的分类 网络地址网络地址的分类 计算机网络相关性能指标速率带宽吞吐量时延时延的种类&#xff1a; 时延带宽积往返时延RTT利用率 概念 计算机网络是指将多台计算机通过通信设备连接起来&#xff0c;实现数据和资源的共享。…

串口小工具(来源网络,源码修改)

从CSDN 中的一位博主的分享做了一些修改 QtSerial 的配和更稳定些 信号和槽 … … 更不容易崩 # This Python file uses the following encoding: utf-8 import sys import timefrom PySide6.QtGui import QIcon, QTextCursor from PySide6.QtWidgets import QApplication, QWi…

第3章_UART 开发基础

文章目录 第3章 UART 开发基础3.1 同步传输与异步传输3.1.1 概念与示例3.1.2 差别 3.2 UART 协议与操作方法3.2.1 UART 协议3.2.2 STM32H5 UART 硬件结构3.2.3 RS485 协议 3.3 UART 编程3.3.1 硬件连接3.3.2 三种编程方式3.3.3 查询方式3.3.4 中断方式3.3.5 DMA 方式 3.4 效率最…

扫描全能王的AI驱动创新与智能高清滤镜技术解析

目录 引言1、扫描全能王2、智能高清滤镜黑科技2.1、图像视觉矫正2.2、去干扰技术 3、实际应用案例3.1、打印文稿褶皱检测3.2、试卷擦除手写3.3、老旧文件处理3.4、收银小票3.5、从不同角度扫描文档 4、用户体验结论与未来展望 引言 在数字化时代背景下&#xff0c;文档扫描功能…

【JavaEE】JVM

文章目录 一、JVM 简介二、JVM 运行流程三、JVM 运行时数据区1、堆&#xff08;线程共享&#xff09;2、Java虚拟机栈&#xff08;线程私有&#xff09;3、本地方法栈&#xff08;线程私有&#xff09;4、程序计数器&#xff08;线程私有&#xff09;5、方法区&#xff08;线程…