Swift 中的动态成员查找
文章目录
- 前言
- 基础介绍
- 基础示例
- 1. 定义一个动态成员访问类:
- 2. 访问嵌套动态成员:
- 使用 KeyPath 的编译时安全性
- KeyPath 用法示例
- KeyPath 进阶使用示例
- 1. 动态访问属性:
- 2. 结合可选属性和 KeyPath:
- 3. 动态 KeyPath 和字典:
- 总结
前言
在 Swift 中,动态成员查找是一种允许在编译时未知成员的情况下,通过字符串名称来访问属性和方法的机制。这在需要与动态数据进行交互,或者在某些情况下进行元编程时非常有用。动态成员查找通过 Swift 的 @dynamicMemberLookup
特性实现。
Glassfy:简化构建、管理和推广应用内购买。从订阅管理 SDK 到付费墙等完整的货币化工具。立即免费构建。
基础介绍
假设我们正在开发一个提供缓存功能的类型,并将其建模为名为 Cache 的结构体。
struct Cache {var storage: [String: Data] = [:]
}
为了访问缓存的数据,我们调用存储属性的下标,该存储属性是 Dictionary 类型提供的。
var cache = Cache()
let profile = cache.storage["profile"]
在这里没有什么特别之处。我们像以前一样通过 Dictionary 类型的下标访问字典。让我们看看如何使用 @dynamicMemberLookup
属性改进 Cache 类型的 API。
@dynamicMemberLookup
struct Cache {private var storage: [String: Data] = [:]subscript(dynamicMember key: String) -> Data? {storage[key]}
}
如上例所示,我们使用 @dynamicMemberLookup
属性标记了 Cache 类型。我们必须实现具有 dynamicMember
参数并返回我们需要的任何内容的下标。
var cache = Cache()
let profile = cache.profile
现在,我们可以更方便地访问 Cache 类型的配置文件数据。我们的 API 的使用者可能会认为配置文件是 Cache 类型的属性,但事实并非如此。
此特性完全在运行时工作,并利用了在点符号后键入的任何属性名称来访问 Cache 类型的下标,该下标具有 dynamicMember 参数。
整个逻辑在运行时运行,编译期间的结果是不确定的。在运行时,完全可以决定应该从下标返回哪些数据以及如何处理 dynamicMember 参数。
基础示例
以下是在 Swift 中使用动态成员查找的一些基本示例:
1. 定义一个动态成员访问类:
@dynamicMemberLookup
struct DynamicMemberExample {subscript(dynamicMember member: String) -> String {return "Accessed dynamic member: \(member)"}
}// 创建实例
let dynamicObject = DynamicMemberExample()// 使用动态成员查找
print(dynamicObject.someProperty) // 输出: Accessed dynamic member: someProperty
print(dynamicObject.someMethod()) // 输出: Accessed dynamic member: someMethod()
在上面的示例中,我们定义了一个结构体 DynamicMemberExample
,使用 @dynamicMemberLookup
注解。这允许我们通过下标方法 subscript(dynamicMember:)
来实现对动态成员的访问。然后我们可以像使用普通成员一样使用动态成员。
2. 访问嵌套动态成员:
@dynamicMemberLookup
struct NestedDynamicMemberExample {struct Inner {subscript(dynamicMember member: String) -> String {return "Accessed dynamic member: \(member)"}}var inner = Inner()
}// 创建实例
let nestedDynamicObject = NestedDynamicMemberExample()// 使用动态成员查找
print(nestedDynamicObject.inner.someProperty) // 输出: Accessed dynamic member: someProperty
在这个示例中,我们在一个嵌套的结构体 Inner
中实现了动态成员查找。然后我们可以通过外部结构体 NestedDynamicMemberExample
的实例访问嵌套结构体的动态成员。
动态成员查找是一种强大的特性,但也要注意,它通常会损失一些类型安全性,因为在编译时不会检查动态成员的存在。使用动态成员查找时,请确保仔细考虑潜在的风险。
请注意,动态成员查找在 Swift 5.1 及更高版本中可用。如果在更早的版本中使用动态成员查找,可能需要进行适当的版本升级。
使用 KeyPath 的编译时安全性
我们唯一能找到的缺点是缺乏编译时安全性。我们可以将 Cache 类型视为代码中键入的任何属性名称。幸运的是,@dynamicMemberLookup
下标的参数不仅可以是 String 类型,还可以是 KeyPath 类型。
@dynamicMemberLookup
final class Store\<State, Action>: ObservableObject {
typealias ReduceFunction = (State, Action) -> State@Published private var state: Stateprivate let reduce: ReduceFunctioninit(initialState state: State,reduce: @escaping ReduceFunction) {self.state = stateself.reduce = reduce}subscript<T>(dynamicMember keyPath: KeyPath<State, T>) -> T {state[keyPath: keyPath]}func send(_ action: Action) {state = reduce(state, action)}}
如上例所示,我们定义了接受强类型 KeyPath 实例的 dynamicMember 参数下标。在这种情况下,我们允许 State 类型的 KeyPath,这有助于我们获得编译时安全性。因为每当我们传递与 State 类型无关的错误 KeyPath 时,编译器都会显示错误。
struct State {
var products: \[String] = \[]
var isLoading = false
}enum Action {
case fetch
}let store: Store\<State, Action> = .init(initialState: .init()) { state, action in
var state = state
switch action {
case .fetch:
state.isLoading = true
}
return state
}print(store.isLoading)
print(store.products)
print(store.favorites) // Compiler error
在上例中,我们通过接受 KeyPath 的下标访问 Store 的私有 state 属性。这看起来与前面的例子类似,但在这种情况下,只要尝试访问 State 类型的不可用属性,编译器就会显示错误。
KeyPath 用法示例
当涉及到 Swift 中的 KeyPath
时,实际的代码示例可以更好地阐明其概念和用法。下面将详细介绍先前提到的示例代码,以更清晰地展示 KeyPath
的用途和编译时安全性。
首先,我们定义一个名为 Person
的结构体,表示一个人的姓名和年龄:
struct Person {let name: Stringlet age: Int
}
接下来,我们创建一个 Person
实例,名为 person
,并为其赋予姓名 “Alice” 和年龄 30:
let person = Person(name: "Alice", age: 30)
然后,我们使用 KeyPath
来引用 Person
实例的属性。我们创建了两个 KeyPath
,一个用于访问姓名属性,另一个用于访问年龄属性:
let nameKeyPath = \Person.name
let ageKeyPath = \Person.age
使用 KeyPath
,我们可以通过下标语法从 person
实例中访问属性值:
let personName = person[keyPath: nameKeyPath] // "Alice"
let personAge = person[keyPath: ageKeyPath] // 30
在这里,我们通过 nameKeyPath
和 ageKeyPath
使用了 KeyPath
,这是编译时类型安全的。如果我们尝试引用不存在的属性,编译器将在编译时捕获错误。
接下来,我们假设有一个数组 people
包含多个 Person
实例。我们可以使用 KeyPath
来对数组中的实例进行映射,以获取所有人的姓名和年龄:
let people = [Person(name: "Alice", age: 30),Person(name: "Bob", age: 25),Person(name: "Charlie", age: 28)
]let names = people.map(\.name) // ["Alice", "Bob", "Charlie"]
let ages = people.map(\.age) // [30, 25, 28]
这里,我们使用 KeyPath
进行了数组的映射,从每个 Person
实例中提取了姓名和年龄。
最后,我们可以使用 KeyPath
对数组中的实例按年龄进行排序:
let sortedPeople = people.sorted(by: \.age)
通过传递 KeyPath
给 sorted(by:)
方法,我们可以按年龄对人员进行排序。
总之,KeyPath
是 Swift 中的一项强大特性,它提供了类型安全的属性和下标访问方式,可以在编译时捕获错误,并具有映射、排序等便利功能。通过理解 KeyPath
的概念和用法,可以写出更安全、更易读和更高效的 Swift 代码。
KeyPath 进阶使用示例
下面介绍一下关于 KeyPath
更多的用法和示例
1. 动态访问属性:
let propertyName = "name"
let dynamicKeyPath = \Person[keyPath: propertyName] // Error: Cannot convert value of type 'String' to expected key path type 'WritableKeyPath<Person, _>'
在这个示例中,我们尝试使用字符串变量创建一个动态的 KeyPath
。然而,这将会导致编译错误,因为编译器需要在编译时确定 KeyPath
的类型。
2. 结合可选属性和 KeyPath:
struct Team {let captain: Person?
}let team = Team(captain: Person(name: "David", age: 32))if let captainName = team.captain?[keyPath: \.name] {print("Captain's name: \(captainName)")
} else {print("No captain assigned")
}
在这个示例中,我们定义了一个 Team
结构体,其中的 captain
属性是可选的 Person
实例。通过使用 KeyPath
,我们可以在安全的情况下访问可选属性。
3. 动态 KeyPath 和字典:
let personKeyPath: KeyPath<Person, String> = \.namelet personDictionary = ["name": "Ella", "age": "28"]
if let name = personDictionary[keyPath: personKeyPath] {print("Name from dictionary: \(name)")
} else {print("Name not found in dictionary")
}
在这个示例中,我们将 KeyPath
与字典结合使用。尽管字典中的键和 KeyPath
类型可能不匹配,但在运行时可以使用动态 KeyPath 访问字典中的值。
KeyPath
是 Swift 中非常有用的一项功能,它提供了类型安全的属性和下标访问方法,可以在编译时捕获错误,还支持映射、排序和与其他类型的组合使用。理解 KeyPath
的概念和用法将为编写更清晰、更安全的 Swift 代码提供强大的工具。
总结
KeyPath
是 Swift 编程语言中的一项强大功能,它为属性和下标提供了类型安全的访问方式,提升了代码的可读性、可维护性和性能。通过使用 KeyPath
,我们可以避免使用字符串进行属性访问,从而减少了因拼写错误而引发的问题。
通过深入理解 KeyPath
的概念和用法,开发者可以编写更具类型安全性、可读性和性能的 Swift 代码。这一功能不仅简化了代码编写,还为我们提供了一种更加优雅的方式来处理属性和下标的访问与操作。
我们学习了如何使用 @dynamicMemberLookup
属性改进特定类型的 API。虽然并不是每个类型都需要它,但可以谨慎使用它来改善 API。
相关文章:

Swift 中的动态成员查找
文章目录 前言基础介绍基础示例1. 定义一个动态成员访问类:2. 访问嵌套动态成员: 使用 KeyPath 的编译时安全性KeyPath 用法示例KeyPath 进阶使用示例1. 动态访问属性:2. 结合可选属性和 KeyPath:3. 动态 KeyPath 和字典ÿ…...
leetcode做题笔记102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 思路一:递归 int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes){int** ans(int**)mal…...

python编写四画面同时播放swap视频
当代技术让我们能够创建各种有趣和实用的应用程序。在本篇博客中,我们将探索一个基于wxPython和OpenCV的四路视频播放器应用程序。这个应用程序可以同时播放四个视频文件,并将它们显示在一个GUI界面中。 C:\pythoncode\new\smetimeplaymp4.py 准备工作…...

用XSIBackup为VMware ESXi打造完美备份方案
文章目录 VMware ESXi 备份方案引言XSIBackup安装步骤1. XSIBackup软件安装2. SSH连接3. 定位到xsibackup目录4. 修改文件权限5. 安装cron查看crontab列表6. 配置备份任务结论VMware ESXi 备份方案 引言 数据就像是我们的生命线,一旦丢失,可能会带来无法挽回的损失。对于那…...

React 项目中引入msal验证以及部分报错处理
功能实现 如何在React 项目中引入msal身份验证, 微软在官网有提供文档支持,文档包含示例和具体使用的教程,地址如下: https://learn.microsoft.com/zh-cn/azure/active-directory/develop/tutorial-v2-nodejs-webapp-msal 照着文…...
Unity3D 2021 使用 SharpZipLib 遇到的安卓打包 I18N 相关问题
在 Unity3D 中,使用 ICSharpCode.SharpZipLib.dll 来做压缩和解压缩,但打包安卓后遇到问题,原因是字符编码程序集被裁减掉了导致。 根据网上搜索,将 UnityEditor 对应目录下的 I18N开头的,比如 I18N.CJK.dll 等系列文…...

软件工程(十五) 行为型设计模式(一)
1、责任链模式 简要说明 通过多个对象处理的请求,减少请求的发送者与接收者之间的耦合。将接受对象链接起来,在链中传递请求,直到有一个对象处理这个请求。 速记关键字 传递职责 类图如下 由类图可以比较容易的看出来,其实就是自己关联自己,形成了一个链,并且自己有…...
【校招VIP】前端算法考点之快慢指针题型
考点介绍: 链表是校招面试里手撕代码出现频度比较高的题型,三线和中小厂会考察简单的链表反转,大厂会进一步考察复杂度和双指针问题,比如中间元素、是否存在环等。 『前端算法考点之快慢指针题型』相关题目及解析内容可点击文章末…...

Docker基础入门:容器数据卷与Dockerfile构建镜像(发布)
Docker基础入门:容器数据卷与Dockerfile构建镜像(发布) 一、docker容器数据卷1.1、使用docker容器数据卷1.2、具名挂载、匿名挂载1.3、如何确定是具名挂载还是匿名挂载 二、使用dockerfile2.1 初识Dockerfile2.2 Dockerfile构建过程2.3 Docke…...
部署问题集合(二十一)从零开始搭建一台NAS服务器(Linux虚拟机)
前言 因工作需要,需要从零通过虚拟机搭建一台NAS服务器,以此记录下来 步骤 1、创建虚拟机 通过VMWare创建一台新虚拟机,虚拟机内存和磁盘自定义,不过建议尽量大一点 2、服务器端配置 查看是否安装有NFS服务:rpm …...

Git小白入门——了解分布式版本管理和安装
Git是什么? Git是目前世界上最先进的分布式版本控制系统(没有之一) 什么是版本控制系统? 程序员开发过程中,对于每次开发对各种文件的修改、增加、删除,达到预期阶段的一个快照就叫做一个版本。 如果有一…...

芯科科技宣布推出下一代暨第三代无线开发平台,打造更智能、更高效的物联网
第三代平台中的人工智能/机器学习引擎可将性能提升100倍以上 Simplicity Studio 6软件开发工具包通过新的开发环境将开发人员带向第三代平台 中国,北京 - 2023年8月22日 – 致力于以安全、智能无线连接技术,建立更互联世界的全球领导厂商Silicon Labs&…...

无涯教程-Android - Intents/Filters
Android Intent 是要执行的操作的抽象描述。它可以与 startActivity 一起启动Activity,将 broadcastIntent 发送给任何BroadcastReceiver组件,并与 startService(Intent)或 bindService(Intent,ServiceConnection,int)与后台服务进…...

NFTScan 正式上线 Base NFTScan 浏览器和 NFT API 数据服务
2023 年 8 月 24 号,NFTScan 团队正式对外发布了 Base NFTScan 基础设施,将为 Base 生态的 NFT 开发者和用户提供简洁高效的 NFT 数据搜索查询服务。NFTScan 作为全球领先的 NFT 数据基础设施服务商,Base 是继 Bitcoin、Ethereum、BNBChain、…...

【Git】测试持续集成——Git+Gitee+PyCharm
文章目录 概述一、使用Gitee1. 注册账号2. 绑定邮箱3. 新建仓库4. 查看项目地址 二、安装配置Git1. 下载安装包2. 校验是否安装成功。3. 配置Git4. Git命令5. Git实操 三、PyCharmGit1. 配置Git2. Clone项目3. 提交文件到服务器4. 从服务器拉取文件 概述 持续集成(…...

《HelloGitHub》第 89 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等,涵盖多种编程语言 …...

多维时序 | Matlab实现LSTM-Adaboost和LSTM多变量时间序列预测对比
多维时序 | Matlab实现LSTM-Adaboost和LSTM多变量时间序列预测对比 目录 多维时序 | Matlab实现LSTM-Adaboost和LSTM多变量时间序列预测对比预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | Matlab实现LSTM-Adaboost和LSTM多变量时间序列预测对比 模型…...

c语言每日一练(12)
前言:每日一练系列,每一期都包含5道选择题,2道编程题,博主会尽可能详细地进行讲解,令初学者也能听的清晰。每日一练系列会持续更新,暑假时三天之内必有一更,到了开学之后,将看学业情…...

用AI + Milvus Cloud搭建着装搭配推荐系统
在上一篇文章中,我们学习了如何利用人工智能技术(例如开源 AI 向量数据库 Milvus Cloud 和 Hugging Face 模型)寻找与自己穿搭风格相似的明星。在这篇文章中,我们将进一步介绍如何通过对上篇文章中的项目代码稍作修改,获得更详细和准确的结果,文末附赠彩蛋。 注:试用此…...

41、springboot 整合 FreeMarker 模版技术
springboot 整合 FreeMarker 模版技术 ★ 整合FreeMarker的自动配置: FreeMarkerAutoConfiguration:负责整合Spring容器和获取FreeMarkerProperties加载的配置信息。FreeMarkerServletWebConfiguration/FreeMarkerReactiveWebConfiguration:…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

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

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...

边缘计算网关提升水产养殖尾水处理的远程运维效率
一、项目背景 随着水产养殖行业的快速发展,养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下,而且难以实现精准监控和管理。为了提升尾水处理的效果和效率,同时降低人力成本,某大型水产养殖企业决定…...

leetcode_69.x的平方根
题目如下 : 看到题 ,我们最原始的想法就是暴力解决: for(long long i 0;i<INT_MAX;i){if(i*ix){return i;}else if((i*i>x)&&((i-1)*(i-1)<x)){return i-1;}}我们直接开始遍历,我们是整数的平方根,所以我们分两…...