Swift 中的动态成员查找

news2024/12/28 20:03:37

请添加图片描述

在这里插入图片描述

文章目录

    • 前言
    • 基础介绍
    • 基础示例
      • 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: State
    private let reduce: ReduceFunction

    init(
        initialState state: State,
        reduce: @escaping ReduceFunction
    ) {
        self.state = state
        self.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: String
    let 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> = \.name

let 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.coloradmin.cn/o/948378.html

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

相关文章

使用calc()调整元素高度或宽度

<style>.parent { display: flex;padding: 0px 5px;width: 600px;height: 200px;background: #ccc;}.children { margin: 10px 10px;/* 减去padding和margin */height: calc(100% - 20px);width: calc(100% - 30px);background: skyblue;}</style><div class&qu…

嵌入式Linux开发实操(十四):SPI接口开发

# 前言 SPI(Serial Peripheral Interface)同UART、I2C、CAN等一样,是MCU/SOC的重要接口,没错,它是个通讯接口,一个串行通讯接口,我们想到了四线接口(CS、CLK、MOSI、MISO) 可以通过CS(ChipSelect)或者SS (Slave Select)线来选择和哪个SPI设备通信,选择就是把这条线…

无涯教程-Android - Linear Layout函数

Android LinearLayout是一个视图组&#xff0c;该视图组将垂直或水平的所有子级对齐。 Linear Layout - 属性 以下是LinearLayout特有的重要属性- Sr.NoAttribute & 描述1 android:id 这是唯一标识布局的ID。 2 android:baselineAligned 此值必须是布尔值&#xff0c;为…

Java中ArrayList.remove(index)漏删的问题

问题描述 ArrayList中数据删除漏删 测试代码如下 public static void main(String[] args) {List<Integer> list new ArrayList<>();for(int i0;i<10;i){list.add(i1);}System.out.println("删除前&#xff1a;list.szie() "list.size());for(i…

什么是跨域问题 ?Spring MVC 如何解决跨域问题 ?

1. 什么是跨域问题 &#xff1f; 跨域问题指的是不同站点之间&#xff0c;使用 ajax 无法相互调用的问题。 跨域问题的 3 种情况&#xff1a; 1. 协议不同&#xff0c;例如 http 和 https&#xff1b; http://127.0.0.1:8080https://127.0.0.1:8080 2. 域名不同&#xff1…

在springboot项目中显示Services面板的方法

文章目录 前言方法一&#xff1a;Alt8快捷键方法二&#xff1a;使用Component标签总结 前言 在一个springboot项目中&#xff0c;通过开启Services面板&#xff0c;可以快速的启动、配置、管理多个子项目。 方法一&#xff1a;Alt8快捷键 1、在idea界面输入Alt8&#xff0c;在…

非科班菜鸡算法学习记录 | 代码随想录算法训练营第50天|| 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

123. 买卖股票的最佳时机III 123. Best Time to Buy and Sell Stock III(英文力扣连接) 知识点&#xff1a;动规 状态&#xff1a;看思路ok 思路&#xff1a; 跟之前股票思路一样&#xff0c;多定义几个状态&#xff0c;此题要买两次&#xff0c;所以算上不操作一共有五个状态…

软件测试案例 | 气象探测库存管理系统的集成测试计划

将经过单元测试的模块按照设计要求连接起来&#xff0c;组成规定的软件系统的过程被称为“集成”。集成测试也被称为组装测试、联合测试、子系统测试或部件测试等&#xff0c;其主要用于检查各个软件单元之间的接口是否正确。集成测试同时也是单元测试的逻辑扩展&#xff0c;即…

聊聊手机导航软件

目录 1.人在外地没有导航的后果 2.传统导航方式 3.手机软件导航的原理 4.手机导航的优点 1.人在外地没有导航的后果 在外地没有导航的情况下&#xff0c;可能会带来以下一些后果&#xff1a; 1. 容易迷路&#xff1a;没有导航指引的情况下&#xff0c;你可能会走错路线&…

常见前端面试之VUE面试题汇总十三

39. Vue 中 key 的作用 vue 中 key 值的作用可以分为两种情况来考虑&#xff1a; 第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元 素&#xff0c;通常会复用已有元素而不是从头开始渲染。因此当使用 v-if 来 实现元素切换的时候&#xff0c;如果切换前后含有相…

普里戈任或持有近10万个比特币

来源&#xff1a;Beincrypto 作者&#xff1a;David Thomas 编译&#xff1a;Shelby 摘要&#xff1a; 一个持有26 亿美元的比特币钱包已被确定可能与俄罗斯军事承包商有联系。 虽是猜测&#xff0c;但据报道「叶夫根尼普里戈任」的不正规商业行为可能受益于比特币的匿名性功能…

中国应试教育市场:挑战与机遇并存,金榜状元引领前行

2023年全国高考报名人数1291万人再次刷新历史纪录&#xff0c;但一本的录取率仅为23%&#xff1b;教育部2021年开始推行中考分流政策&#xff0c;只有约为50%初中毕业生可以升入普通高中&#xff1b;“双减”政策的推行&#xff0c;使得高考升学的压力提前到中考阶段&#xff0…

ATFX汇市:美元指数已连续六周收阳,需警惕超买风险

ATFX汇市&#xff1a;7月14日当周&#xff0c;因为6月份爆冷的非农就业报告&#xff0c;美元指数大跌2.26%。奇怪的是&#xff0c;在此之后&#xff0c;美元指数进入超强牛市状态。截至上周&#xff0c;已经连续6周收出阳K线&#xff0c;累计涨幅达4.24%&#xff0c;最高触及10…

CI/CD 持续集成 持续交付

CI&#xff08;Continuous integration&#xff09;持续集成 参考&#xff1a;https://www.jianshu.com/p/2132949ff84a 持续集成是指多名开发者在开发不同功能代码的过程当中&#xff0c;可以频繁的将代码行合并到一起并切相互不影响工作。 持续集成的目的&#xff0c;是让…

多店铺智能客服,助力店铺销量倍增

近年来电商发展得非常快速&#xff0c;市场竞争也是愈发激烈了。商家不仅需要提高产品和服务的质量&#xff0c;还要争取为自己获取更多的曝光&#xff0c;以此来分散运营的风险和降低经营的成本&#xff0c;所以越来越多的商家也开始转向多平台多店铺运营。但即使运营多个平台…

Shiro整合SpringBoot,实战下的应用场景

文章目录 前言一、springBootshiro环境准备1.数据库2.ssmp环境搭建3.实体类4.三层搭建5.初始化测试数据 二、Shiro过滤器1.Shiro认证过滤器2.Shiro授权过滤器 三、springBootshiro身份认证1.创建Realm,重写认证方法doGetAuthenticationInfo2.创建shiro配置类3.Postman测试 四、…

并发(读写锁,线程池)03 详细讲解

读写锁 独占锁&#xff1a;一次只能被一个线程占有 共享锁。多个线程可以同时占有 class Apple{public static void main(String[] args) {Mycache mycachenew Mycache();for (int i 0; i <5 ; i) {final int tempi;new Thread(()->{mycache.put(temp"",tem…

北京985学校,交叉学科考英一数三408

北京师范大学(B) 考研难度&#xff08;☆☆☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、23专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文1096字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 北…

ChatGPT⼊门到精通(5):ChatGPT 和Claude区别

⼀、Claude介绍 Claude是Anthropic开发的⼀款⼈⼯智能助⼿。 官⽅⽹站&#xff1a; ⼆、Claude能做什么 它可以通过⾃然语⾔与您进⾏交互,理解您的问题并作出回复。Claude的主要功能包括: 1、问答功能 Claude可以解答⼴泛的常识问题与知识问题。⽆论是历史上的某个事件,理科…

飞天使-python的字符串转义字符元组字典等

文章目录 基础语法数据类型python的字符串运算符输入和输出 数据结构列表与元组字典与集合 参考文档 基础语法 数据类型 数值型 &#xff0c;整数 浮点型 布尔型&#xff0c; 真假&#xff0c; 假范围 字符型 类型转换python的字符串 了解转义字符一些基本的运算 \ 比如一行…