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-)

相关推荐
战族狼魂37 分钟前
XCode 发起视频 和 收到视频通话邀请实现双语功能 中文和俄语
swift
UXbot4 小时前
2026年AI全链路产品开发工具对比:5款从创意到上线一站式平台深度解析
前端·ui·kotlin·软件构建·swift·原型模式
报错小能手7 小时前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
Highcharts.js7 小时前
企业级可视化生态系统|关于Highcharts集成的前端框架、后端编程语言与生态
开发语言·javascript·python·前端框架·编辑器·编程语言·highcharts
报错小能手1 天前
ios开发方向——swift并发进阶核心 async/await 详解
开发语言·ios·swift
用户79457223954132 天前
【Lottie】让设计稿上的动效直接"活"在 App 里
swiftui·swift
Mr_Tony3 天前
Swift 中的 Combine 框架完整指南(含示例代码 + 实战)
开发语言·swift
用户79457223954134 天前
【SnapKit】优雅的 Swift Auto Layout DSL 库
swiftui·swift
报错小能手4 天前
ios开发方向——swift内存基础
开发语言·ios·swift
东坡肘子4 天前
苹果的罕见妥协:当高危漏洞遇上“拒升”潮 -- 肘子的 Swift 周报 #130
人工智能·swiftui·swift