当前位置: 首页 > news >正文

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 缺乏的能力:用代码生成代码!

一般来说,我们有两种办法来解决此事:

  1. 在运行时动态生成代码,ruby 和 Python 就非常精于此道(所以 ruby 和 Python 之类的语言根本不需要所谓的宏);
  2. 由编译器(预处理器)当家做主在编译前生成代码,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 宏&#xff08;Swift Macro&#xff09;。为此&#xff0c;苹果特地用 2 段视频&#xff08;入门和进阶&#xff09;颇为隆重的介绍了它。 那么到底 Swift 宏是什么&#xff1f;有什么用&…...

linux常见资源查询命令(持续更新)

年纪大了&#xff0c;很多命令记不住了&#xff0c;但偶尔也需要用到&#xff0c;通过搜索也需要点时间&#xff0c;特此记录。 不同操作系统命令会有所区别&#xff0c;下面是大部分时候工作的机器系统&#xff1a; CentOS release 7.5 (Final)Kernel \r on an \m 1、实时查…...

JavaWeb:文件上传1

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…...

C++ 中的异常处理机制是怎样的?

异常处理的基本概念&#xff1a; 异常: 程序在运行时发生的错误或意外情况。 抛出异常: 使用 throw 关键字将异常传递给调用堆栈。 捕获异常: 使用 try-catch 块捕获和处理异常。 异常类型: 表示异常类别的标识符。 异常处理流程&#xff1a; 抛出异常: 当检测到错误或意…...

SwiftUI-基础入门

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

C++builder中的人工智能(20):如何在C++中开发一个简单的Hopfield网络

在AI技术的发展历史中&#xff0c;模式识别模型是最伟大的AI技术之一&#xff0c;尤其是从像素图像中读取文本。其中一个是Hopfield网络&#xff08;或称为Ising模型的神经网络或Ising–Lenz–Little模型&#xff09;&#xff0c;这是一种递归神经网络形式&#xff0c;由John J…...

video2gif容器构建指南

一、介绍 1.项目概述 Video2Gif 项目旨在提供一种便捷的方式&#xff0c;让用户能够将视频中的精彩片段快速转换为 GIF 动画。GIF 动画因其循环播放、文件体积小等特点&#xff0c;在社交媒体、聊天工具中广泛应用&#xff0c;用于表达情感、分享趣事等。 2.核心功能 视频导…...

探秘Spring Boot中的@Conditional注解

文章目录 1. 什么是Conditional注解&#xff1f;2. 为什么需要Conditional注解&#xff1f;3. 如何使用Conditional注解&#xff1f;4. Conditional注解的高级用法5. 注意事项6. 结语推荐阅读文章 在Spring Boot的世界里&#xff0c;配置的灵活性和多样性是至关重要的。有时候&…...

树形dp总结

这类题型在 dp 中很常见&#xff0c;于是做一个总结吧&#xff01;&#xff01;&#xff01; 最经典的题&#xff1a;没有上司的舞会 传送门&#xff1a;没有上司的舞会 - 洛谷 状态表示&#xff1a; dp[i][0] 为 以 i 为根的子树中&#xff0c;选择 i 节点的最大欢乐值 d…...

【算法一周目】双指针(2)

目录 有效三角形的个数 解题思路 C代码实现 和为s的两个数字 解题思路 C代码实现 三数之和 解题思路 C代码实现 四数之和 解题思路 C代码实现 有效三角形的个数 题目链接&#xff1a;611. 有效三角形的个数题目描述&#xff1a;给定一个包含非负整数的数组nums&…...

vue内置方法总结

目录 1. 生命周期钩子方法 2. 响应式系统方法 3. DOM 更新方法 4. 事件处理方法 5. 访问子组件和 DOM 元素 6. 数据观察方法 7. 其他方法 1. 生命周期钩子方法 这些方法在 Vue 实例的不同生命周期阶段自动调用。 beforeCreate&#xff1a; 在实例初始化之后&#xff0c…...

面向对象分析与设计

前言: 感觉书本上和线上课程, 讲的太抽象, 不好理解, 但软件开发不就是为了开发应用程序吗?! 干嘛搞这么抽象,对吧, 下面是个人对于软件开发的看法, 结合我的一些看法, 主打简单易懂, 当然,我一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#中&#xff0c;SendMessage方法是一个强大的工具&#xff0c;它允许我们与Windows API交互&#xff0c;模拟键盘和鼠标事件。本文将详细介绍如何使用SendMessage方法来发送鼠标和键盘消息。 1. SendMessage方法概述 SendMessage是Windows API中的一个函数&#xff0c;它用…...

VMware安装黑苹果后ICLOUD_UNSUPPORTED_DEVICE(不支持的Icloud设备)

修改文件 关闭虚拟机找到虚拟机文件中以.vmx结尾的文件编辑内容&#xff08;补充缺失&#xff09; board-id "Mac-551B86E5744E2388" hw.model.reflectHost "FALSE" hw.model "MacBookPro14,3" serialNumber.reflectHost "FALSE"…...

Python | Leetcode Python题解之第542题01矩阵

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

【计算机网络】【传输层】【习题】

计算机网络-传输层-习题 文章目录 10. 图 5-29 给出了 TCP 连接建立的三次握手与连接释放的四次握手过程。根据 TCP 协议的工作原理&#xff0c;请填写图 5-29 中 ①~⑧ 位置的序号值。答案技巧 注&#xff1a;本文基于《计算机网络》&#xff08;第5版&#xff09;吴功宜、吴英…...

【LeetCode】【算法】55. 跳跃游戏

LeetCode 99 - 55. 跳跃游戏 题目 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回true&#xff1b;否则&#xff0c;返回 …...

华为:hcia综合实验

一、拓扑图 二、实验要求 1. pc地址请自行规划&#xff0c;vlan已给出 2. 服务器地址自行规划&#xff0c;vlan&#xff0c;网段已给出 3. 交换机互联链路捆绑保证冗余性 4. 内网pc网关集中于核心交换机&#xff0c;交换机vlan 40互联路由器 ,地址网段已给出 5.配置静态路由实…...

MyBatis与MyBatis-Plus(基础)

MyBatis-Plus的优势 在 Spring Data JPA 已经很方便的情况下&#xff0c;有时仍然选择使用 MyBatis-Plus 的核心原因主要有以下三点&#xff1a; 1. 复杂 SQL 控制能力更强 MyBatis-Plus 允许直接编写和优化 SQL&#xff0c;适合复杂查询、精细化 SQL 控制的场景。特别是在性…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?

在工业自动化持续演进的今天&#xff0c;通信网络的角色正变得愈发关键。 2025年6月6日&#xff0c;为期三天的华南国际工业博览会在深圳国际会展中心&#xff08;宝安&#xff09;圆满落幕。作为国内工业通信领域的技术型企业&#xff0c;光路科技&#xff08;Fiberroad&…...

6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙

Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...