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

在这里,我们通过 nameKeyPathageKeyPath 使用了 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)

通过传递 KeyPathsorted(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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/64330.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL8.0.22安装过程记录(个人笔记)

1.点击下载MySQL 2.解压到本地磁盘&#xff08;注意路径中不要有中文&#xff09; 3.在解压目录创建my.ini文件 文件内容为 [mysql] # 设置mysql客户端默认字符集 default-character-setutf8[mysqld] # 设置端口 port 3306 # 设计mysql的安装路径 basedirE:\01.app\05.Tool…

《爵士乐史》乔德.泰亚 笔记

第一章 【美国音乐的非洲化】 【乡村布鲁斯和经典布鲁斯】 布鲁斯&#xff1a;不止包括忧愁、哀痛 十二小节布鲁斯特征&#xff1a; 1.乐型&#xff08;A:主、B:属、C/D:下属&#xff09;&#xff1a;A→A→B→A→C→D→A→A 2.旋律&#xff1a;大三、小三、降七、降五 盲人…

Matlab(GUI程式设计)

目录 1.MatlabGUI 1.1 坐标区普通按钮 1.1.1 对齐组件 1.1.2 按钮属性 1.1.3 脚本说明 1.1.4 选择呈现 1.3 编译GUI程序 在以前的时候&#xff0c;我们的电脑还是这样的 随着科技的不断进步&#xff0c;我们的电脑也发生着翻天覆地的改变1990s&#xff1a; 在未来&#xff0c…

优化爬虫请求:如何选择合适的爬虫ip轮换策略?

在进行爬虫任务时&#xff0c;使用隧道爬虫ip并采用合适的轮换策略可以提高稳定性和效率。选择合适的隧道爬虫ip轮换策略可以优化您的爬虫请求过程。 1、考量目标网站特点 不同网站对于频繁请求可能有不同限制或反爬机制。 了解目标网站是否存在IP封禁、验证码等问题&#xff…

2359. 找到离给定两个节点最近的节点;1781. 所有子字符串美丽值之和;2406. 将区间分为最少组数

2359. 找到离给定两个节点最近的节点 核心思想:统计node1和node2分别到每个点的距离&#xff0c;然后在枚举每个点统计结果。关键在于如何统计node到每个点的距离&#xff0c;首先是初始化为inf很重要&#xff0c;因为在枚举的时候&#xff0c;因为是inf代表了这个节点无法到达…

VC++使用Microsoft Speech SDK进行文字TTS朗读

Microsoft Speech SDK下载地址 https://www.microsoft.com/en-us/download/details.aspx?id10121 需要msttss22L.exe、SpeechSDK51.exe、SpeechSDK51LangPack.exe三个&#xff0c;下载后全部安装 使用VS2005建立一个win32控制台项目 朗读"hello word"、中文“你好”…

Flink的checkpoint是怎么实现的?

分析&回答 Checkpoint介绍 Checkpoint容错机制是Flink可靠性的基石,可以保证Flink集群在某个算子因为某些原因(如 异常退出)出现故障时,能够将整个应用流图的状态恢复到故障之前的某一状态,保证应用流图状态的一致性。Flink的Checkpoint机制原理来自“Chandy-Lamport alg…

Docker 的快速使用

ubuntu安装 centos安装 安装完毕之后执行一下这条命令&#xff0c;可以避免每次使用docker命令都需要sudo权限 sudo usermod -aG docker $USER阿里云docker镜像加速 DockerHub 遇到不懂或者不会使用的命令可以使用docker --help查看文档 docker --help 如&#xff1a; dock…

OFDM 系统在 AWGN 信道下对不同载波频率偏移 (CFO) 的 BER 灵敏度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Metinfo6.0.0任意文件读取【漏洞复现】

文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现代码审计漏洞点 1.5、深度利用EXP编写 1.6、漏洞挖掘1.7修复建议 1.1、漏洞描述 漏洞名称&#xff1a;MetInfo任意文件读取 漏洞简介&#xff1a;MetInfo是一套使用PHP和MySQL开发的内容管理系统&#xff0c;其…

Java实现根据商品ID获取京东商品详情数据,1688商品详情接口,1688API接口封装方法

要通过京东的API获取商品详情数据&#xff0c;您可以使用京东开放平台提供的接口来实现。以下是一种使用Java编程语言实现的示例&#xff0c;展示如何通过京东开放平台API获取商品详情&#xff1a; 首先&#xff0c;确保您已注册成为京东开放平台的开发者&#xff0c;并创建一…

经管博士科研基础【12】包络定理

当我们知道一个函数的最优解时&#xff0c;我们要求解这一个函数的值函数关于函数中某一个参数的导数&#xff0c;那么就可以使用包络定理。 1. 无约束条件下的包络定理 函数在其极值点处对一个参数&#xff08;参数不是自变量&#xff09;取偏导数的结果&#xff0c;等价于这…

Node.js crypto模块 加密算法

背景 微信小程序调用飞蛾热敏纸打印机&#xff0c;需要进行参数sig签名校验&#xff0c;使用的是sha1进行加密 // 通过crypto.createHash()函数&#xff0c;创建一个hash实例&#xff0c;但是需要调用md5&#xff0c;sha1&#xff0c;sha256&#xff0c;sha512算法来实现实例的…

小兔鲜商02

npm i vueuse/core -fvue插件使用&#xff1a; 许多公用的全局组件&#xff0c;&#xff0c;可以通过插件注册进去&#xff0c;就不用一个一个导入组件&#xff0c;&#xff0c; import XtxSkeleton from /components/library/xtx-skeletonexport default {install (app) {// …

linux操作系统中的动静态库(未完)

1. 静态库与动态库 静态库&#xff08;.a&#xff09;&#xff1a;程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库动态库&#xff08;.so&#xff09;&#xff1a;程序在运行的时候才去链接动态库的代码&#xff0c;多个程序共享使用库的…

unity 跨屏显示

1.代码 /*Type:设置分辨率*/ using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.Runtime.InteropServices;public class ScreenManager : MonoBehaviour {[HideInInspector]//导入设置窗口函数 [DllImport("…

Redis之MoreKey问题及Scan命令解读

目录 MoreKey问题讨论 Scan命令 Sscan命令 Hscan命令 Zscan命令 MoreKey问题讨论 keys * 查看当前库所有key 对于海量数据执行key *会造成严重服务卡顿、影响业务。在实际环境中最好不要使用。生产制造过程中keys * / flushdb/flushall等危险命令以防止误删误用。 大量的…

kotlin实现猜数游戏

游戏规则 1.程序随机生成一个1到100的数字&#xff0c;作为MagicNumber 2.用户根据提示输入数据&#xff0c;只有三次机会输入数据 代码 代码很简单&#xff0c;使用了let内置函数 fun main() {//生成随机数可以使用java的方法//val magicNumber Random().nextInt(11)val ma…

如何确认linux的包管理器是yum还是apt,确认之后安装其他程序的时候就需要注意安装命令

打开终端 输入apt&#xff0c;下图中提示未找到命令&#xff0c;则基本上包管理工具就是用yum的 输入yum&#xff0c;我们看到有打印信息&#xff0c;则说明包管理工具是yum的&#xff0c;离线安装命令使用rpm

斥资4亿,收购这家WLAN厂商,结果……

晚上好&#xff0c;我的网工朋友 不少朋友可能有隐形&#xff0c;2019年的时候&#xff0c;Juniper花费4.05亿美元&#xff0c;收购WiFi初创公司Mist Systems。 Mist Systems是一家买无线产品起家的公司&#xff0c;由前思科高管创建的。主打的产品是“AI-Driven WLAN”&…