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

相关推荐
青峰讲运维12 小时前
OpenStack 存储深度解析|Cinder 块存储 + Swift 对象存储原理与完整实操(Yoga)
开发语言·openstack·swift
2501_9151063213 小时前
Windows 上开发 Unity iOS,使用 KXBuild 进行真机调试、性能采样与打包 ipa
windows·vscode·unity·ios·个人开发·swift·敏捷流程
AI前端工程狮14 小时前
Agent 不会报错,只会变笨:给 MCP Server 补上契约测试和回放测试
人工智能·编程语言
codigger2 天前
前端没有被杀死,被杀死的是"只会把设计稿变成页面"
前端·ai·ai编程·编程语言·全栈·#人工智能
2501_915918412 天前
Windows 上开发 React Native iOS,真机调试、Fast Refresh 与打包 ipa
windows·vscode·react native·ios·个人开发·swift·敏捷流程
szp20052 天前
中文字幕烧进视频后整片空白:CoreText 低报行高,导出照样成功
ios·swift
全栈弄潮儿2 天前
Python实战第13期:模块与包
后端·python·编程语言
codigger3 天前
别再手动催 AI 干活了:一文读懂 Loop Engineering(循环工程)
ai·程序员·编程语言·#人工智能
2501_915106323 天前
iOS 单元测试怎么跑?用 KXApp 在 VS Code 里跑 XCTest 和 Swift Testing
ide·vscode·ios·单元测试·个人开发·swift·敏捷流程
东坡肘子3 天前
当应用不再属于特定操作系统 -- 肘子的 Swift 周报 #159
人工智能·swiftui·swift