Swift宏

     Swift 宏在 WWDC 2023 的 Swift 5.9 版本中引入,简单来说它允许我们在编译时生成重复代码,它还允许我们在编译之前动态地操作项目的 Swift 代码,从而允许我们在编译时注入额外的功能,使我们的应用程序的代码库更易于阅读且更高效地编码。

    最初的Swift版本其实并不支持宏,这其实也和Swift语言的设计理念有关,C语言中的宏应用广泛,但是编译时展开的特性会是代码的可读性下降,也会增加代码的漏洞风险。Swift秉承安全、易理解、易使用的设计初衷,并没有引入宏的概念。但宏的元编程能力可以大大的提高编程的灵活性和复用性,Swift在5.9版本中重新引入了宏功能,并且是以一种全新的方式来定义和实现宏,在提供灵活性的同时保证代码的安全性和可靠性。但这也有一些缺陷,相比与C语言的宏,Swift中的宏的定义非常抽象,实现复杂,不太利于开发者进行理解。

使用Swift宏的特性:

1.宏会在编译代码前进行代码转换,即预编译阶段进行处理。

2. 宏在展开时,永远只会增加代码,不会修改或删除原始的代码。(重点)

3. 宏的输入和输出都会经过编译器的检查,保证其语法正确,并且如果宏展开后的实现发现异常,也会被处理为编译时异常。

Swift宏分为两类,独立宏、附加宏

其中,独立宏单独出现,单独使用,不会附加到任何声明(可以理解为原始代码)上。附加宏则需要配合声明一起使用,通常是为了向原代码中增加一些功能。从特性上看,独立宏与C语言的宏有些类似,做简单的代码展开或静态替换很方便。附加宏则更像是一种装饰器模式的应用,为原始逻辑进行包装,附加功能。这两种宏从声明到用法上都有区别。

独立宏

独立宏使用"#"来调用,因此当你在代码中看到#相关的语法时,就要意识到这是一个宏,且是一个独立宏。标准库中默认提供了一些独立宏可以直接使用,例如:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        print(#file, #function, #line)
        #warning("系统宏,显示警告信息")
    }
}

上面代码中,#file,#function,#line和#warning都是独立宏,前3个宏无参数,在编译时分别替换为当前文件名、当前函数名和当前行号,#warning宏有参数,用来为告诉编译器这里展示一条警告信息。这些宏因为是标准库中的,我们无法查看展开后的样子,如果是自定义宏则可以直接展开查看,后面我们再介绍。

附加宏

使用”@“来调用附加宏,附加宏用来补充其所声明的代码,为原始代码添加新的功能,附加宏比较复杂。

宏的声明、定义与实现

      Swift语言和C语言的一大区别在于Swift一般无需做声明,如函数、变量、类等,直接定义即可使用。但宏却不同,宏必须进行声明,声明的主要作用是指定宏的名称、参数以及类型和使用场景。

      与普通的Swift功能代码不同,每个宏都是一个单独的Swift包,在工程中我们可以创建一个新的Package,选择Swift Macro。

      宏的实现依赖于swift-syntax包,Xcode会自动帮我们加载好依赖。创建好的的Package会自动生成模版文件,我们只需要关心Sources和Tests文件夹下的内容即可。自动生成的模板中的宏是使用了swift-syntax包的Swift源代码静态分析能力,略为复杂,增加了理解宏本身的难度。这里我们可以不理会这部分,专注于宏本身的逻辑。

宏的声明

独立宏声明

   独立宏使用@freestanding来进行声明,在声明宏时,需要指定宏的角色。独立宏有两种角色:

expression:创建一段有返回值的代码。

declaration:声明类宏,用来创建声明类的代码。

例如我们声明一个角色为expression的宏,如下:

@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "DQCodableMacroMacros", type: "StringifyMacro")

代码中,@freestanding(expression)指定了当前宏是一个表达式角色的独立宏,#externalMacro是Swift内置的一个宏,指定了当前宏所对应的模块名以及类型标识。

声明一个declaration角色的宏如下:

@freestanding(declaration, names: arbitrary)
public macro MakeStatic(_ name: String) = #externalMacro(module: "MyMacroMacros", type: "MakeStaticMacro")

需要注意,宏在指定角色时,可以通过names参数来对要使用的符号进行定义,以上面的宏声明为例,MakeStatic的作用是会生成一个静态变量,因此会在原代码中新增符号,但是变量的名称是由参数决定的,因此需要将names参数设置为arbitrary,表示要生成的符号是不定的。

names参数可填为:

1 named(xxx) 具体的符号名称。

2 overloaded 对原符号的重载

3 prefixed(xxx) 增加前缀

4 suffixed(xxx) 增加后缀

5 arbitrary 动态符号名称

附加宏的声明

附加宏使用@attached来进行声明,与独立宏类似,其也需要指定角色:

peer:对等角色,与所附加的原代码在相同的层级上增加代码,例如增加函数的重载。

member:成员角色,为所附加的原代码增加内部成员,如增加属性,方法等。

memberAttribute:成员属性角色,为所附加的源代码的内部成员增加属性。

accessor:访问器角色,为所附加的源代码增加Getter,Setter方法等。

extension(之前为conformance,最新swift版本修改为extension):遵守着角色,为所附加的源代码增加协议和约束。

我们先来定义一个peer角色类型的宏,用来实现一个自动生成的重载函数,此重载函数会增强原函数的功能,添加函数的执行时间日志。如下:

@attached(peer, names: overloaded)
public macro OverrideForAPM() = #externalMacro(module: "MyMacroMacros", type: "OverrideForAPMMacro")

这里我们将names指定为了overloaded,表示对原符号的重载操作。

member角色的宏通常用来为类或结构增加成员变量或方法等,声明示例如下:

//添加codable协议的两个函数
@attached(member, names: named(init(from:)), named(encode(to:)), arbitrary)

该宏定义指在为类活着结构体增加两个doable协议的两个方法init(from:)和encode(to)

accessor角色宏用在具体的成员上,用来增加访问器逻辑,例如下面的声明,此宏将为访问器自动生成计算属性逻辑:

@attached(accessor)
public macro GetLog() = #externalMacro(module: "MyMacroMacros", type: "GetLogMacro")

extension宏用来为原结构增加一些协议或遵守一些规则,例如我们可以定义一个宏,来让所修饰的修改自动实现Codable协议:

@attached(extension, conformances: Codable)
public macro Codable() = #externalMacro(module: "DQCodableMacroMacros", type: "Codable")

其中conformances参数指定要遵守的协议,因为我们同时要对协议进行实现,会引入新的符号,因此需要names参数中也指明。

通过以上,我们了解的Swift宏的简介与特性以及如何生声明一个宏,下一篇具体看看如何对Swift宏进行实现

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值