Swift 宏(Macro)入门趣谈(一)
概述
苹果在去年 WWDC 23 中就为 Swift 语言新增了“其利断金”的重要小伙伴 Swift 宏(Swift Macro)。为此,苹果特地用 2 段视频(入门和进阶)颇为隆重的介绍了它。
那么到底 Swift 宏是什么?有什么用?它和 C/C++ 语言中的宏又有什么异同呢?本系列博文将会尝试为小伙伴们揭开 Swift 宏的神秘面纱。
在本篇博文中,您将学到如下内容:
- 概述
- 1. 从一个”尴尬“的小例子聊起
- 2. 什么是 Swift 宏?它与其它语言中的宏有何不同?
- 总结
相信学完本系列博文后,Swift Macro 会从大家心中的“阳春白雪”变为“阳阿薤露”,小伙伴们必可以将它们运用的“如臂使指”。
那还等什么呢?Let‘s go!!!😉
1. 从一个”尴尬“的小例子聊起
其实早在去年苹果祭出 Swift 5.9 时,Swift 语言本身就已经加入了超多先进和现代化的特性。
自从今年 Swift 大版本进化到 6 之后,几乎所有秃头码农们都不约而同的认为 Swift 不仅已经前所未有地成熟和稳定,而且它的能力也变得史无前例的强大。真可谓:“多平台,台台出彩;多系统;统统有戏”,貌似 Swift 已经变得“登峰造极”、“气吞山河”了。
果真如此吗?
更多关于 Swift 5.9 和 Swift 6.0 语言的全方位介绍,请小伙伴们移步我的《Swift 语言开发精讲》专栏和苹果 WWDC 24 官方站点观赏精彩的文章和视频:
- 《Swift 语言开发精讲》
- WWDC 2024
咱先别把话说满,虽说 Swift 现今已足够强大,但它真的是无所不能,丝毫不存在所谓的“阿格硫斯之踵”吗?
答案是否定的!下面我们就举一个小“栗子”让看似强大的 Swift “一秒破功”。
假设我们要实现一个很简单的功能:按类型实例的属性排序。
struct Item {let name: Stringlet nickname: String?let age: Intlet power: Double
}
在上面的 Item 中存在若干属性,我们希望写一个通用的排序方法对 Item 集合排序:
struct Model {let items: [Item]func sortItemsBy<Value: Comparable>(keyPath: KeyPath<Item, Value>, sortOrder: SortOrder = .forward) throws -> [Item] {items.sorted(using: SortDescriptor(keyPath, order: sortOrder))}
}
如上代码所示,我们在模型 Model 中创建了一个 sortItemsBy() 方法按 Item 中任意属性来排序 [Item] 数组。
比如,我们可以分别依据 Item 的 name 和 age 属性来生成不同排序后的数组:
let model = Model(items: [])
let itemsByName = try! model.sortItemsBy(keyPath: \.name)
let itemsByAge = try! model.sortItemsBy(keyPath: \.age, sortOrder: .reverse)
这样很好很强大,不过目前排序功能有点小问题:就是我们无法排序类型为可选值的属性:
let itmesByNickname = try! model.sortItemsBy(keyPath: \.nickname)
如您所见,当我们试图按 nickname 属性排序 Item 数组时,编译器就会大声抱怨。原因前面已经说了,因为 nickname 属性的类型是 String?(即 Optional<String>):
对此,我们可以提出相应的解决方法,即另外写一个可选(Optional)参数版本的排序方法:
func sortItemsBy<Value: Comparable>(keyPath: KeyPath<Item, Value?>, sortOrder: SortOrder = .forward) throws -> [Item] {items.sorted(using: SortDescriptor(keyPath, order: sortOrder))
}
这个方法与之前的几乎一毛一样,唯一不同之处在于 KeyPath 的 Value 是可选类型。
现在我们就有点“哭笑不得”了,对于排序功能我们竟然要写两个方法,即使它们几乎如出一辙。
当然对于这样简单的排序功能,我们完全可以想办法“剥离”外部的方法而直接使用 [Item] 上的 sorted() 方法。
有的小伙伴们可能会想出下面这种“左右逢源”的排序实现:
func sortItemBy<Value: Comparable>(keyPath1: KeyPath<Item, Value>?, keyPath2: KeyPath<Item, Value?>?, sortOrder: SortOrder = .forward) throws -> [Item] {if let keyPath1 {items.sorted(using: SortDescriptor(keyPath1, order: sortOrder))} else if let keyPath2 {items.sorted(using: SortDescriptor(keyPath2, order: sortOrder))} else {throw MyError("至少要有一个 KeyPath 不为 nil!")}
}
但是这样一来,我们仍然在方法内部造成了代码重复并且整体实现根本毫无优雅性可言。
与此类似的场景在偏静态的 Swift 语言中,绝对会让我们心余力绌,坐如针毡。这就是 Swift 缺乏的能力:用代码生成代码!
一般来说,我们有两种办法来解决此事:
- 在运行时动态生成代码,ruby 和 Python 就非常精于此道(所以 ruby 和 Python 之类的语言根本不需要所谓的宏);
- 由编译器(预处理器)当家做主在编译前生成代码,C/C++ 宏的强项;
将来有没有暂且不说,目前 Swift 还没有 ruby 和 Python 那种运行时动态“造码”的能力。不过从 WWDC 23 那年开始,苹果推出了 Swift 宏专注于在编译时静态“造码”。
我们的最终目标是实现下面这个 @nilable 宏,它可以生成对应版本的 KeyPath<Root, Value?> 排序方法:
看我们如何在这一共 5 篇系列博文中步步为营,最后铁杵成针的吧!
2. 什么是 Swift 宏?它与其它语言中的宏有何不同?
在官方的开发文档中,苹果就为 Macro 的特性定了基调:在编译时生成代码。 具体来说,Swift Macro 在编译时可以根据现有代码转换或生成新代码,这样做的最大好处就是避免重复(DRY)
Swift 编译器会在我们的逻辑代码编译前将其中的任何宏展开(Expands)。Swift 宏有两个重要的特性:
- 它绝不会删除已存在的代码;
- 它绝不会修改(其它)已存在的代码;
其实,在 WWDC 23 之前苹果就已经在代码中使用过海量的特殊宏,从最常见的 #function、#file、#error 宏,到 @main 、@available、@discardableResult 宏等等。我感觉在 WWDC 23 之前的某个时刻,苹果一定就有把这些特殊宏升级为通用宏的壮志凌云。
关于 Swift 宏的详细文档,大家可以到苹果官方开发站点一窥究竟:
- Swift Macros
虽然都是“宏字辈”,但是 Swift 中的宏还是与 C/C++ 等语言中的宏有些区别的:
- Swift 中的宏完全参与到 Swift 的类型系统中,可以在编译时保证类型安全;
- 苹果确保宏的展开过程是一个沙箱(Sandbox)操作,不会带来安全漏洞或泄露系统中用户的隐私;
- 在宏展开发生错误时,Swift Macros 提供了更多的机制来帮助用户快速了解错误原因和解决办法;
- 我们可以为宏编写单元测试,增强它的鲁棒性;
因为 C/C++ 中的宏只是一个简单的字符串字面值替换器,所以无论是用户编写和编译器支持起来都不算太难,但是 Swift 的宏就完全是另一回事了。
想要系统学习 Swift 的小伙伴们,欢迎来我的 《Swift语言开发精讲》专栏逛一逛哦。
- 《Swift 语言开发精讲》
在了解了 Swift 宏之后,在下一篇博文中我们就来看看它能做些什么以及不要用它们做什么?
总结
在本篇博文中,我们讨论了 Swift 宏的基本概念,以及它与 C/C++ 语言中的宏有何不同。
感谢观赏,我们下一篇见!😎
相关文章:

Swift 宏(Macro)入门趣谈(一)
概述 苹果在去年 WWDC 23 中就为 Swift 语言新增了“其利断金”的重要小伙伴 Swift 宏(Swift Macro)。为此,苹果特地用 2 段视频(入门和进阶)颇为隆重的介绍了它。 那么到底 Swift 宏是什么?有什么用&…...
linux常见资源查询命令(持续更新)
年纪大了,很多命令记不住了,但偶尔也需要用到,通过搜索也需要点时间,特此记录。 不同操作系统命令会有所区别,下面是大部分时候工作的机器系统: CentOS release 7.5 (Final)Kernel \r on an \m 1、实时查…...

JavaWeb:文件上传1
欢迎来到“雪碧聊技术”CSDN博客! 在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将…...
C++ 中的异常处理机制是怎样的?
异常处理的基本概念: 异常: 程序在运行时发生的错误或意外情况。 抛出异常: 使用 throw 关键字将异常传递给调用堆栈。 捕获异常: 使用 try-catch 块捕获和处理异常。 异常类型: 表示异常类别的标识符。 异常处理流程: 抛出异常: 当检测到错误或意…...

SwiftUI-基础入门
开发OS X 图形应用界面时有三种实现方式:XIB、Storyboard、SwiftUI。Storyboard基于XIB做了优化,但XIB基本被放弃了,而SwiftUI是苹果公司后来开发的一套编程语言,用来平替Objective-C。虽然现在Swift 6 还是有些不完善的地方&…...

C++builder中的人工智能(20):如何在C++中开发一个简单的Hopfield网络
在AI技术的发展历史中,模式识别模型是最伟大的AI技术之一,尤其是从像素图像中读取文本。其中一个是Hopfield网络(或称为Ising模型的神经网络或Ising–Lenz–Little模型),这是一种递归神经网络形式,由John J…...

video2gif容器构建指南
一、介绍 1.项目概述 Video2Gif 项目旨在提供一种便捷的方式,让用户能够将视频中的精彩片段快速转换为 GIF 动画。GIF 动画因其循环播放、文件体积小等特点,在社交媒体、聊天工具中广泛应用,用于表达情感、分享趣事等。 2.核心功能 视频导…...
探秘Spring Boot中的@Conditional注解
文章目录 1. 什么是Conditional注解?2. 为什么需要Conditional注解?3. 如何使用Conditional注解?4. Conditional注解的高级用法5. 注意事项6. 结语推荐阅读文章 在Spring Boot的世界里,配置的灵活性和多样性是至关重要的。有时候&…...
树形dp总结
这类题型在 dp 中很常见,于是做一个总结吧!!! 最经典的题:没有上司的舞会 传送门:没有上司的舞会 - 洛谷 状态表示: dp[i][0] 为 以 i 为根的子树中,选择 i 节点的最大欢乐值 d…...

【算法一周目】双指针(2)
目录 有效三角形的个数 解题思路 C代码实现 和为s的两个数字 解题思路 C代码实现 三数之和 解题思路 C代码实现 四数之和 解题思路 C代码实现 有效三角形的个数 题目链接:611. 有效三角形的个数题目描述:给定一个包含非负整数的数组nums&…...
vue内置方法总结
目录 1. 生命周期钩子方法 2. 响应式系统方法 3. DOM 更新方法 4. 事件处理方法 5. 访问子组件和 DOM 元素 6. 数据观察方法 7. 其他方法 1. 生命周期钩子方法 这些方法在 Vue 实例的不同生命周期阶段自动调用。 beforeCreate: 在实例初始化之后,…...
面向对象分析与设计
前言: 感觉书本上和线上课程, 讲的太抽象, 不好理解, 但软件开发不就是为了开发应用程序吗?! 干嘛搞这么抽象,对吧, 下面是个人对于软件开发的看法, 结合我的一些看法, 主打简单易懂, 当然,我一IT界小菜鸟, 对软件开发的认识也很浅显, 这个思维导图也仅仅是现阶段我的看…...
lineageos-19 仓库群遍历,打印第一条git log
lineageos-19 仓库群遍历,打印第一条git log RepoLsRootD/app4/lineage19_oneplus6 LogF/app4/wiki/repo_head_log_ls-lineageos19.1.log rm -v $LogF && \ cd $RepoLsRootD && \ find . -type l -path "*/*.git" -not -path "./.repo/*"…...

详解基于C#开发Windows API的SendMessage方法的鼠标键盘消息发送
在C#中,SendMessage方法是一个强大的工具,它允许我们与Windows API交互,模拟键盘和鼠标事件。本文将详细介绍如何使用SendMessage方法来发送鼠标和键盘消息。 1. SendMessage方法概述 SendMessage是Windows API中的一个函数,它用…...
VMware安装黑苹果后ICLOUD_UNSUPPORTED_DEVICE(不支持的Icloud设备)
修改文件 关闭虚拟机找到虚拟机文件中以.vmx结尾的文件编辑内容(补充缺失) board-id "Mac-551B86E5744E2388" hw.model.reflectHost "FALSE" hw.model "MacBookPro14,3" serialNumber.reflectHost "FALSE"…...

Python | Leetcode Python题解之第542题01矩阵
题目: 题解: class Solution:def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:m, n len(matrix), len(matrix[0])# 初始化动态规划的数组,所有的距离值都设置为一个很大的数dist [[10**9] * n for _ in range(m)]…...

【计算机网络】【传输层】【习题】
计算机网络-传输层-习题 文章目录 10. 图 5-29 给出了 TCP 连接建立的三次握手与连接释放的四次握手过程。根据 TCP 协议的工作原理,请填写图 5-29 中 ①~⑧ 位置的序号值。答案技巧 注:本文基于《计算机网络》(第5版)吴功宜、吴英…...
【LeetCode】【算法】55. 跳跃游戏
LeetCode 99 - 55. 跳跃游戏 题目 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回true;否则,返回 …...

华为:hcia综合实验
一、拓扑图 二、实验要求 1. pc地址请自行规划,vlan已给出 2. 服务器地址自行规划,vlan,网段已给出 3. 交换机互联链路捆绑保证冗余性 4. 内网pc网关集中于核心交换机,交换机vlan 40互联路由器 ,地址网段已给出 5.配置静态路由实…...
MyBatis与MyBatis-Plus(基础)
MyBatis-Plus的优势 在 Spring Data JPA 已经很方便的情况下,有时仍然选择使用 MyBatis-Plus 的核心原因主要有以下三点: 1. 复杂 SQL 控制能力更强 MyBatis-Plus 允许直接编写和优化 SQL,适合复杂查询、精细化 SQL 控制的场景。特别是在性…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...