Swift 中强大的 Key Paths(键路径)机制趣谈(上)

在这里插入图片描述

概览

小伙伴们可能不知道:在 Swift 语言中隐藏着大量看似“其貌不扬”实则却让秃头码农们“高世骇俗”,堪称卧虎藏龙的各种秘技。

在这里插入图片描述

其中,有一枚“不起眼”的小家伙称之为键路径(Key Paths)。如若将其善加利用,必将在实际撸码中大放异彩,如虎添翼!

在本篇博文中,您将学到如下内容:

  • 概览
  • 1. 一窥门径:键路径(Key Paths)初步
  • 2. 功能快速简化之妙用
  • 3. 将键路径当做方法传递
  • 总结

本篇和下一篇皆为看似“平淡无奇”的键路径“凤凰涅槃”、逆袭重生的“励志”博文!到底如何?且看分解!

闲言少叙,Let’s change our destiny against the heavens!!!😉


1. 一窥门径:键路径(Key Paths)初步

我们知道 Swift 语言最初的设计重点是编译时安全和静态类型。因此,它势必会缺乏那些更加关注运行时语言(如 Objective-C、Ruby 和 JavaScript)中常见的那种动态特性。例如,在 Objective-C 中,我们可以在运行时动态访问对象中的任何属性和方法,甚至交换、修改其相关的实现。


想要了解更多 Swift 动态机制内容的小伙伴们,请移步如下链接观赏精彩的内容:

  • 『番外篇二』Swift “黑魔法”之动态获取类实例隐藏属性的值
  • SwiftUI 利用 Swizz 黑魔法为系统创建的默认对象插入新协议方法(五)
  • SwiftUI 利用 Swizz 黑魔法为系统创建的默认对象插入新协议方法(六)
  • SwiftUI 如何在运行时从底层动态获取任何 NSObject 对象实例
  • 『番外篇五』SwiftUI 进阶之如何动态获取任意视图的 tag 和 id 值

虽然这种缺乏动态性的特点可能恰恰是 Swift 如此强大的主要缘由,因为它可以帮助我们编写可预测性更强的逻辑,并且有更大的概率撸出“正确”的代码。

不过,有时能够以更动态的方式处理我们的实现夙愿也会非常有用。

谢天谢地!进化中的 Swift 语言不断汲取着越来越多本质上更动态的功能,同时也仍然专注于类型的安全性,这其中一个不可或缺的特性便是键路径(Key Paths)。

在这里插入图片描述

在 Swift 中广义的键路径是指一种动态访问和修改对象属性的机制,而狭义的键路径则用来表示特定根类型上特定属性值的类型。

在这里插入图片描述

一般来说存在三种键路径:

  1. KeyPath: 最常见的形式,用来提供到某一类型特定属性的只读路径;
  2. WritableKeyPath: 用值语义(value semantics)提供到某一类型特定属性的读写路径(因而,该类型的实例也必须是可写的);
  3. ReferenceWritableKeyPath: 和 WritableKeyPath 类似,不过只能用在引用类型上(比如类);

除了上面最常见的三种键路径类型以外,还有其它一些键路径。它们主要被用于减少内部代码重复或帮助类型抹除(Type erase)等情况,限于篇幅就不在本文中介绍了,

如果想要进一步了解这些额外键路径类型,请小伙伴们移步如下链接观赏进一步的内容:

  • Key-Path Expressions
  • KeyPath Documentation

在初步了解键路径的基本概念之后,下面就让我们深入探寻一番如何使用关键路径,以及它们因何而有趣、又因何而强大吧。

2. 功能快速简化之妙用

假设我们正在构建一款应用程序,它允许用户阅读来自网页的内容。我们设计了一个 Article 模型用来表示 Web 页面中对应的文章,如下所示:

struct Article {let id: UUIDlet source: URLlet title: Stringlet body: String
}

在大多数情况下,每当我们使用这样的模型数组时,通常希望从数组每个元素中提取一块数据以形成新的数组 —— 例如,在下面两个示例中我们从一组文章(Article)中收集了所有的 id 和 source:

let articleIDs = articles.map { $0.id }
let articleSources = articles.map { $0.source }

虽然上面的代码并没有什么错,不过由于我们的愿望只是单纯地从数组元素的单个属性中提取值,所以使用闭包看似有些大材小用了。

在这里,换为键路径会更加恰如其分。

extension Sequence {func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {return map { $0[keyPath: keyPath] }}
}

如上代码所示,我们为序列(Sequence)增加了一个协议扩展方法 map,该方法的参数为序列元素任意属性的键路径。我们在 map 方法的实现中巧妙利用 Swift 下标( subscript)语法糖“有胆有识”的访问了序列元素属性的值。

这样一来,我们即可以用非常 Nice 的语法来提取序列元素任意属性的内容啦!所以之前的代码可以重构为如下形式了:

let articleIDs = articles.map(\.id)
let articleSources = articles.map(\.source)

虽然这让秃头码农们觉得很酷,不过键路径真正熠熠生辉的地方是当它们用于构建更复杂表达式的时候:比如在对数组排序时。

众所周知,Swift 标准库能够自动对包含 Sortable 元素的任何序列进行排序,但对于所有其它类型,我们必须提供自己的排序闭包。然而,使用键路径我们也可以轻而易举地添加对基于可比较键路径序列元素进行排序的支持。

与之前类似,我们将在 Sequence 协议上添加一个扩展方法,它的作用是将给定的键路径转换为排序表达式闭包:

extension Sequence {func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {return sorted { a, b inreturn a[keyPath: keyPath] < b[keyPath: keyPath]}}
}

有了上面的“铺垫”,我们现在只需给出想要排序的键路径,即可优哉游哉地对任何类型元素的序列进行排序啦。

假若我们正在构建的 App 需要处理任何形式的可排序列表,例如包含播放列表的音乐应用程序 —— 这将非常方便,因为我们现在可以根据任何可比较属性(甚至嵌套属性)对列表进行排序了:

playlist.songs.sorted(by: \.name)
playlist.songs.sorted(by: \.dateAdded)
playlist.songs.sorted(by: \.ratings.worldWide)

上面代码看起来就像在优雅地添加“甜美的”语法糖,这既可以使处理序列复杂的代码更容易阅读,也可以帮助减少代码重复(DRY):因为小伙伴们现在可以“为所欲为”的向任何属性重用相同的排序逻辑啦,很棒哦!

3. 将键路径当做方法传递

一个好消息是:从 Swift 5.2 开始,上面 Sequence 扩展中的 map 方法已不再需要,因为键路径如今可以自动地转换为方法啦(converted into functions)!

这可能只是 Swift 语言进化中的一小步,但却是键路径“功成名就”的一大步!因为它会使序列上功能闭包的调用看起来更加“青出于蓝” —— 因为我们现在可以直接传递该属性的键路径了:

struct Movie {var name: Stringvar isFavorite: Bool...
}let movies: [Movie] = loadMovies()// 等价于 movies.map { $0.name }
let movieNames = movies.map(\.name)// 等价于 movies.filter { $0.isFavorite }
let favoriteMovies = movies.filter(\.isFavorite)

总结

在本篇博文中,我们先是介绍了 Swift 语言中“简约却不简单”的键路径(Key Paths)机制,接着讨论了将它用来简化逻辑以及当成方法(functions)传递的美妙瞬间。

我们将在下一篇博文中继续介绍如何用键路径超越对象实例,特例化(specialize)数据模型;以及用可写键路径彻底摆脱“引用循环”,让简化代码“一蹴而就”。

感谢观赏,下一篇再会喽!😎

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

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

相关文章

java面试课程-SpringIOC部分源码解析

1.SpringIOC的refresh源码解析 核心&#xff1a; 核心使用的是&#xff1a; 需要完成配置类的解析&#xff0c;各种BeanFactoryProcessor的注册。还有写国际化配置的初始化。Web容器的内部构造。 上面几个方法是refresh方法的内容。注意可以与applicationContext里的内容一起…

个人博客|PHP源码|支持多国语言切换

一. 前言 今天小编给大家带来了一款可学习&#xff0c;可商用的&#xff0c;支持多国语言的个人博客网站源码&#xff0c;支持二开&#xff0c;无加密。此博客相当简洁&#xff0c;也适合海外。详细界面和功能见下面视频演示。 如果您正好有此需求源码&#xff0c;请联系小编…

七大排序算法的深入浅出(java篇)

&#x1f341; 个人主页&#xff1a;爱编程的Tom&#x1f4ab; 本篇博文收录专栏&#xff1a;Java专栏&#x1f449; 目前其它专栏&#xff1a;c系列小游戏 c语言系列--万物的开始_ 等等 &#x1f389; 欢迎 &#x1f44d;点赞✍评论⭐收藏&#x1f496;三连支…

【Rust入门教程】hello world程序

文章目录 前言Hello World程序运行总结 前言 对于学习任何一种新的编程语言&#xff0c;我们都会从编写一个简单的Hello World程序开始。这是一个传统&#xff0c;也是一个开始。在这篇文章中&#xff0c;我们将一起学习如何在Rust中编写你的第一个程序&#xff1a;Hello Worl…

解决pip安装时的“SyntaxError: invalid syntax”错误

项目场景&#xff1a; 项目中有新的成员加入时&#xff0c;第一步就是安装开发环境&#xff0c;然而往往同样的机器、同样的配置&#xff0c;我们却总能遇到各种各样不同的问题。 今天分享一个简单的操作问题。 问题描述 项目用到pandas&#xff0c;安装pandas时遇到Syntax…

linux 安装腾讯会议和解决ubuntu打开腾讯会议提示:不兼容 wayland 协议

一. 下载腾讯会议安装包 腾讯会议下载链接 二. 命令行安装 cd [安装包路径] sudo dpkg -i TencentMeeting_0300000000_3.19.1.400_x86_64_default.publish.deb 三. 打开腾讯会议提示无法支持wayland 协议 解决方法: 打开终端 sudo vi /etc/gdm3/custom.conf打开 #Wayland…

GNeRF代码复现

https://github.com/quan-meng/gnerf 之前一直去复现这个代码总是文件不存在&#xff0c;我就懒得搞了&#xff08;实际上是没能力哈哈哈&#xff09; 最近突然想到这篇论文重新试试复现 一、按步骤创建虚拟环境安装各种依赖等 二、安装好之后下载数据&#xff0c;可以用Blen…

Rocky Linux 9 系统OpenSSH CVE-2024-6387 漏洞修复

Rocky Linux 9系统 OpenSSH CVE-2024-6387 漏洞修复 1、漏洞修复2、修复思路3、修复方案3.1、方案一3.2、方案二 4、总结5、参考 1、漏洞修复 CVE-2024-6387&#xff1a;regreSSHion&#xff1a;OpenSSH 服务器中的远程代码执行&#xff08;RCE&#xff09;&#xff0c;至少在…

【Java学习笔记】java图形界面编程

在前面的章节中&#xff0c;我们开发运行的应用程序都没有图形界面&#xff0c;但是很多应用软件&#xff0c;如Windows下的Office办公软件、扑克牌接龙游戏软件、企业进销存ERP系统等&#xff0c;都有很漂亮的图形界面。素以需要我们开发具有图形界面的软件。 Java图形界面编程…

【大数据】什么是数据融合(Data Fusion)?

目录 一、数据融合的定义 二、数据融合的类型 三、数据融合的挑战 四、数据融合的方法 五、数据融合的关键环节 1.数据质量监控指标的制定和跟踪 2.异常检测和处理机制 3.实时数据监测与反馈机制 4.协同合作与知识共享 一、数据融合的定义 数据融合&#xff08;Data Fusion&…

介绍一些好玩且实用的开源的AI工具

介绍一些好玩且实用的开源的AI工具 随着人工智能技术的迅猛发展&#xff0c;开源社区涌现出了许多关于AI的项目&#xff0c;这些项目不仅展示了技术的创新力&#xff0c;也为开发者提供了丰富的工具和资源。本文将介绍几个既有趣又实用的开源人工智能工具&#xff0c;它们不仅…

vue组件深入介绍之插槽

了解插槽之前请先了解vue组件基础及注册 Vue2官网介绍 Vue3官网介绍 1、vue2插槽介绍 在2.6.0中&#xff0c;具名插槽和作用域插槽引入了一个新的统一语法&#xff08;v-slot指令&#xff09;。它将取代slot和slot-scope&#xff1b; Vue 实现了一套内容分发的 API&#xf…

奇瑞被曝强制加班,“896”成常态且没有加班费

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 7 月 2 日消息&#xff0c;一位认证为“奇瑞员工”的网友近期发帖引发热议&#xff0c;奇瑞汽车内部存在强制加班行为&#xff0c;每周加班时长需大于 20 小时并且没有加班费&#xff0c;仅补贴 10 元…

elementui中@click短时间内多次触发,@click重复点击,做不允许重复点击处理

click快速点击&#xff0c;发生多次触发 2.代码示例&#xff1a; //html<el-button :loading"submitLoading" type"primary" click"submitForm">确 定</el-button>data() {return {submitLoading:false,}}//方法/** 提交按钮 */sub…

昇思25天学习打卡营第6天|数据变换 Transforms

学习目标&#xff1a;熟练掌握数据变换操作 熟悉mindspore.dataset.transforms接口 实践掌握常用变换 昇思大模型平台学习心得记录&#xff1a; 一、关于mindspore.dataset.transforms 1.1 变换 mindspore.dataset.transforms.Compose将多个数据增强操作组合使用。 mindspo…

OBD诊断(ISO15031) 04服务

文章目录 功能简介ISO 9141-2、ISO 14230-4和SAE J1850的诊断服务定义1、清除/重置与排放相关的诊断信息请求消息定义2、请求与排放相关的DTC响应消息定义3、报文示例 ISO 15765-4的诊断服务定义1、请求与排放相关的DTC请求消息定义2、请求与排放相关的DTC响应消息定义3、否定响…

信息安全体系架构设计

对信息系统的安全需求是任何单一安全技术都无法解决的&#xff0c;要设计一个信息安全体系架构&#xff0c;应当选择合适的安全体系结构模型。信息系统安全设计重点考虑两个方面&#xff1b;其一是系统安全保障体系&#xff1b;其二是信息安全体系架构。 1.系统安全保障体系 安…

linux下Java11无jre文件夹的问题

项目升级需要更高级的Java版本&#xff0c;于是下载了jdk-11.0.22_linux-x64_bin.tar.gz&#xff0c;解压后jdk-11.0.22下没有jre&#xff0c;导致eclipse下“build path”无法加载jre。 Java11以上版本不在提供jre&#xff0c;Java11安装后&#xff0c;需要如下处理&#xff1…

可充电纽扣电池ML2032充电电路设计

如图&#xff0c;可充电纽扣电池ML2032充电电路设计。 图中二极管是为了防止电流倒灌&#xff0c; 电阻分压出3.66v&#xff0c;再减掉二极管压降&#xff08;约0.4v)得3.26V&#xff0c;加在电池正负极充电。 随着电池电量的积累&#xff0c;充电电流逐步减小&#xff0c;极限…

探索迁移学习:通过实例深入理解机器学习的强大方法

探索迁移学习&#xff1a;通过实例深入理解机器学习的强大方法 &#x1f341;1. 迁移学习的概念&#x1f341;2. 迁移学习的应用领域&#x1f341;2.1 计算机视觉&#x1f341;2.2 自然语言处理&#xff08;NLP&#xff09;&#x1f341;2.3 医学图像分析&#x1f341;2.4 语音…