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:…...

每天 26,315 美元罚款?交通安全局要求特斯拉提供 Autopilot数据
根据美国国家公路交通安全管理局(NHTSA)最近的特别命令,特斯拉公司被要求提供关于其自动驾驶功能Autopilot的相关信息。这一命令是继NHTSA于2021年8月启动初步评估后,在2022年6月升级为正式调查的一部分,NHTSA近期对特…...

3d激光slam建图与定位(2)_aloam代码阅读
1.常用的几种loam算法 aloam 纯激光 lego_loam 纯激光 去除了地面 lio_sam imu激光紧耦合 lvi_sam 激光视觉 2.代码思路 2.1.特征点提取scanRegistration.cpp,这个文件的目的是为了根据曲率提取4种特征点和对当前点云进行预处理 输入是雷达点云话题 输出是 4种特征点…...

Java 8 新特性——Lambda 表达式(2)
一、Java Stream API Java Stream函数式编程接口最初在Java 8中引入,并且与 lambda 一起成为Java开发里程碑式的功能特性,它极大的方便了开放人员处理集合类数据的效率。 Java Stream就是一个数据流经的管道,并且在管道中对数据进行操作&…...

MES管理系统中常用的数据模型有哪些
在MES管理系统项目中,数据建模对于生产过程的监控、分析和管理具有至关重要的作用。本文将介绍一些常见的MES管理系统数据建模方面,并阐述它们在生产过程中的重要性和应用。 1、产品数据模型是MES系统中的基础模块之一。它涵盖了产品的基本信息、规格和属…...

ARM DIY(三)板载串口和 LCD 调试
前言 今天焊接两大关键输入输出设备:串口和屏幕。 串口 串口部分使用 CP2102N 芯片(USB 转 TTL),这样用一根数据线连接板子和 PC 就可以直接调试了。 焊接 CP2102 和 Type C 上电调试,串口可以正常输入输出。 看来…...

计算机网络-笔记-第一章-计算机网络概述
目录 一、第一章——计算机网络概述 1、因特网概述 (1)网络、互联网、因特网 (2)因特网发展的三个阶段 (3)因特网服务的提供者(ISP) (4)因特网标准化工…...

Oracle-day4:分组查询(带条件)、DDL建表、约束、主从表
一、内容回顾 /*------------------内容回顾------------------------上周内容回顾--1、单表的基础查询--A、select * from emp;--B、列的运算 --数字类型运算 - * /--函数运算 mod ceil floor round upper lower--C、取别名--列、表达书取别名--*表示所有的列和列同时存在时…...

(详解)数据结构-----------栈与队列 c语言实现
本章将会详细讲解以下知识点: 目录 一:栈 1:栈的定义,栈的特点 2:用什么结构来实现栈与原因的分析? 3: (超详解)栈的常用接口并且附上测试用例 二:队列 1:队列的定义,队列的特点 2:用什么结…...

前端文件、图片直传OOS、分片上传、el-upload上传(vue+elementUI)
前言:基于天翼云的面相对象存储(Object-Oriented Storage,OOS),实现小文件的直接上传,大文件的分片上传。 开发文档地址:网址 上传之前的相关操作:注册账户,创建 AccessKeyId 和 AccessSecretKey之后&…...

java自动登录 selenium 自动登录并获取cookie
选择操作网页 我用的edge,谷歌我的版本太高没有对应的驱动… 下载Edge的驱动程序,直接解压就好里面只有一个.exe文件 https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ 复制即用,看注释 import com.alibaba.fastjs…...