Swift 宏(Macro)入门趣谈(三)

概述

苹果在去年 WWDC 23 中就为 Swift 语言新增了"其利断金"的重要小伙伴 Swift 宏(Swift Macro)。为此,苹果特地用 2 段视频(入门和进阶)颇为隆重的介绍了它。

那么到底 Swift 宏是什么?有什么用?它和 C/C++ 语言中的宏又有什么异同呢?本系列博文将会尝试为小伙伴们揭开 Swift 宏的神秘面纱。

在本篇博文中,您将学到如下内容:

  1. Swift 宏的组织结构

相信学完本系列博文后,Swift Macro 会从大家心中的"阳春白雪"变为"阳阿薤露",小伙伴们必可以将它们运用的"如臂使指"。

那还等什么呢?Let's go!!!;)


4. Swift 宏的组织结构

目前来说,在 Xcode 16 中宏的实现是承载于 Swift 包(Swift Package)之上的。

用 Swift Package 来组织宏的好处是可以利用强大的 Swift 包管理器(SPM)来驾驭、管理和分发宏代码,同时也借助包(模块 Module)来做逻辑隔离。但是这样一来,即使再简单的 Swift 宏我们也必须套上一个"硕大"的 Package 外套,让大家略微觉得有些不爽。反观一下 C/C++ 中的宏便见分晓:

c 复制代码
#define STR(x) #x

int main() {
    printf("%s\n", STR(11+11));
    return 0;
}

从 Xcode 宏项目模版的代码组织可以看到,它依次可以分为如下几个部分:

  • Swift 包(Swift Package)的描述文件(Package.swift);
  • 宏接口的定义(MyMacro.swift);
  • 测试宏的客户端代码(main.swift);
  • 宏主体的实现(MyMacroMacro.swift);
  • 宏的单元测试(MyMacroTests.swift);
  • Swift 宏所依赖的语法包 swift-syntax;

为了做最少的功打造出一款"冰魂素魄"般的 Swift 自定义宏,我们主要是在宏接口、宏客户端以及宏主体实现上做文章的。

具体来说,为了实现自定义 Swift 宏,我们首先需要定义宏接口:

swift 复制代码
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MyMacroMacros", type: "StringifyMacro")

Swift 宏接口除了能够告知编译器自己是怎样的一个宏以外,它还是连接自定义宏语义与宏模块(上面说过,目前是在 Swift 包里)中具体宏实现的纽带。

接下来,我们需要按照接口的格式在客户端编写宏的调用代码:

swift 复制代码
let (result, code) = #stringify(a + b)

最后,我们必须敛容屏气,谦恭仁厚的实现宏本身的展开代码。


理论上说,不写单元测试仅通过宏客户端的测试代码也可以一步步实现自定义宏。但是实际上,我们还是需要利用单元测试来触发宏展开时的断点,从而更好的验证自定义宏实际的展开逻辑是否正确。

由于篇幅所限,本系列博文不讨论 Swift 宏的单元测试。


根据我们实现 Swift 宏的不同种类,我们需要创建遵守不同宏协议的类型,并实现其中对应的方法。

还记得之前那个 #stringify 宏吗?我们需要为它创建一个遵守 ExpressionMacro 协议的结构,并实现其中唯一的 expansion() 方法:

swift 复制代码
public struct StringifyMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        guard let argument = node.arguments.first?.expression else {
            fatalError("compiler bug: the macro does not have any arguments")
        }

        return "(\(argument), \(literal: argument.description))"
    }
}

细心的小伙伴们可能发现了:上面 expansion() 方法返回的是"字符串",按照方法签名不是应该返回 ExprSyntax 类型吗?

实际上 expansion() 方法最后的返回语句可以等价替换成下面一句代码:

swift 复制代码
return ExprSyntax("(\(argument), \(literal: argument.description))")

看到了吗?其实 ExprSyntax 有转换字符串值的超能力(类似一个接收字符串值的构造器),它是之前导入 swift-syntax 包中的语法构造器(SwiftSyntaxBuilder)帮忙实现的:

swift 复制代码
extension SyntaxParseable {
    public init(stringInterpolation: SyntaxStringInterpolation) {
    self = stringInterpolation.sourceText.withUnsafeBufferPointer { buffer in
      var parser = Parser(buffer)
      let result = Self.parse(from: &parser)
      return result
    }
    if self.hasError {
      self.logStringInterpolationParsingError()
    }
  }
}

Swift 宏实现中包含大量与此类似的自动转换机制,我们可以将字符串转换为特定的语法实体,也可以把语法实体转换为字符串。在后面解决方案的实现中,我们会看到于此相关的代码。

现在,大家对 Swift 宏在 Xcode 项目中的组织架构以及代码构成已经基本了解了。俗话说得好:"自己动手,快乐不愁",下面到了我们撸起袖子亲自小试牛刀的时候了。

在下一篇博文中,我们就来看看如何"白手起家"打造一款自动生成排序方法的 Swift 自定义宏。

总结

在本篇博文中,我们讨论了 Xcode 项目中 Swift 宏的组织结构,并且介绍了想要撸出自已心仪的 Swift 宏需要经历哪些步骤。

感谢观赏,我们下一篇不见不散!8-)

相关推荐
iOS阿玮2 小时前
不想被苹果卡审最好错开这两个提审时间
uni-app·app·apple
程序员岳焱4 小时前
14.Java反射机制:解锁动态编程的魔法之门
java·后端·编程语言
InternLM5 小时前
论文分类打榜赛Baseline:ms-swift微调InternLM实践
swift·internlm·书生
YungFan20 小时前
SwiftUI-Markdown渲染
swiftui·swift
我现在不喜欢coding1 天前
swift为什么会需要mutating关键字
swift
Mirageef1 天前
aardio 图像处理
编程语言
大熊猫侯佩1 天前
用接地气的例子趣谈 WWDC 24 全新的 Swift Testing 入门(一)
单元测试·swift·wwdc
大熊猫侯佩1 天前
Xcode 16 中 Swift Testing 的参数化(Parameterized)机制趣谈
单元测试·swift·apple
catxiao1 天前
Swift Task 结构化并发
swift