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.mzph.cn/bicheng/39854.shtml

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

相关文章

Spring事务十种失效场景

首先我们要明白什么是事务&#xff1f;它的作用是什么&#xff1f;它在什么场景下在Spring框架下会失效&#xff1f; 事务&#xff1a;本质上是由数据库和程序之间交互的过程中的衍生物,它是一种控制数据的行为规则。有几个特性 1、原子性&#xff1a;执行单元内&#xff0c;要…

pjsip环境搭建、编译源码生成.lib库

使用平台&#xff1a; windows qt(5.15.2) vs(2019)x86 pjsip版本以及第三方库使用 pjsip 2.10 ffmpeg4.2.1 sdl2.0.12pjsip源码链接&#xff1a; https://github.com/pjsip/pjproject源码环境配置 首先创建两个文件夹&#xff0c;分别是include、lib其中include放置ff…

p2p、分布式,区块链笔记: 通过libp2p的Kademlia网络协议实现kv-store

Kademlia 网络协议 Kademlia 是一种分布式哈希表协议和算法&#xff0c;用于构建去中心化的对等网络&#xff0c;核心思想是通过分布式的网络结构来实现高效的数据查找和存储。在这个学习项目里&#xff0c;Kademlia 作为 libp2p 中的 NetworkBehaviour的组成。 以下这些函数或…

Java8 - Stream API 处理集合数据

Java 8的Stream API提供了一种功能强大的方式来处理集合数据&#xff0c;以函数式和声明式的方式进行操作。Stream API允许您对元素集合执行操作&#xff0c;如过滤、映射和归约&#xff0c;以简洁高效的方式进行处理。 下面是Java 8 Stream API的一些关键特性和概念&#xff…

windows安装Gitblit还是Bonobo Git Server

Gitblit 和 Bonobo Git Server 都是用于托管Git仓库的工具&#xff0c;但它们是基于不同平台的不同软件。 Gitblit 是一个纯 Java 写的服务器&#xff0c;支持托管 Git&#xff0c;Mercurial 和 SVN 仓库。它需要 Java 运行环境&#xff0c;适合在 Windows、Linux 和 Mac 平台…

Android 输入系统 InputStage

整体流程如上所说&#xff0c;简要归纳如下&#xff1a; 输入法之前的处理 输入法处理 输入法之后处理 综合处理 InputStage将输入事件的处理分成若干个阶段&#xff08;Stage&#xff09;, 如果当前有输入法窗口&#xff0c;则事件处理从 NativePreIme 开始&#xff0c;否…

SpringBoot MongoTemplate使用详解

前面文章讲了 SpringBoot整合MongoDB JPA使用&#xff1a;https://blog.csdn.net/qq_42402854/article/details/139973336 在项目中&#xff0c;通常会 JPA语法与 MongoTemplate两者结合使用&#xff0c;特别是针对复杂动态条件查询时&#xff0c;MongoTemplate更加友好。 Spr…

主流国产服务器操作系统技术分析

主流国产服务器操作系统 信创 "信创"&#xff0c;即信息技术应用创新&#xff0c;作为科技自立自强的核心词汇&#xff0c;在我国信息化建设的进程中扮演着至关重要的角色。自2016年起步&#xff0c;2020年开始蓬勃兴起&#xff0c;信创的浪潮正席卷整个信息与通信技…

GNeRF代码复现

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

virtualbox+Ubuntu部分窗口显示错乱

如下图&#xff1a; 窗口标题显示错乱&#xff0c;跟一般乱码不一样。 解决办法&#xff1a; 在virtualbox设置中&#xff0c;显示选项卡&#xff0c;取消勾选启用3D加速 也可参考此链接&#xff1a;linux ubuntu 中vscode中央窗口显示出现异常/显示错误_开发工具-CSDN问答

打卡第一天

今天是参加算法训练营的第一天&#xff0c;希望我能把这个训练营坚持下来&#xff0c;希望我的算法编程题的能力有所提升&#xff0c;不再面试挂了&#xff0c;面试总是挂编程题&#xff0c;记录我leetcode刷题数量&#xff1a; 希望我通过这个训练营能够实现两份工作的无缝衔接…

自动化任务工具 -- zTasker v1.94 绿色版

软件简介 zTasker 是一款功能强大的自动化任务管理软件&#xff0c;以其简洁易用、一键式操作而著称。软件体积小巧&#xff0c;启动迅速&#xff0c;提供了超过100种任务类型和30多种定时/条件执行方法&#xff0c;能够满足用户在自动化方面的多样化需求。 zTasker 支持定时任…

从全连接到卷积

一、全连接到卷积 1、卷积具有两个原则&#xff1a; 平移不变性&#xff1a;无论作用在哪个部分&#xff0c;它都要有相同的作用&#xff0c;而不会随着位置的改变而改变 局部性&#xff1a;卷积核作用处&#xff0c;作用域应该是核作用点的周围一小部分而不作用于更大的部分 …

OBD诊断(ISO15031) 04服务

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

专题二:Spring源码编译

目录 下载源码 配置Gradle 配置环境变量 配置setting文件 配置Spring源码 配置文件调整 问题解决 完整配置 gradel.properties build.gradle settiings.gradel 在专题一&#xff1a; Spring生态初探中我们从整体模块对Spring有个整体的印象&#xff0c;现在正式从最…

基于Hadoop平台的电信客服数据的处理与分析③项目开发:搭建基于Hadoop的全分布式集群---任务6:安装并配置Hadoop

任务描述 项目的运行环境为基于Hadoop的全分布式模式集群。 任务的主要内容为安装Hadoop分布式集群环境。 任务指导 Hadoop集群需要整个集群所有节点安装的Hadoop版本保持一致&#xff0c;并且拥有相同的配置 具体配置步骤如下&#xff1a; 1. 解压缩Hadoop的压缩包 2. …

AI产品经理需要哪些必备技能?如何成为AI产品经理?

1.AI产品经理是什么 回答这个问题前我们首先得理清楚什么是AI产品经理&#xff0c;它和传统的互联网产品经理有什么区别。 1.1 AI产品经理职责 主要职责一方面是规划如何将成熟的AI技术应用在各个领域不同场景中&#xff0c;提升原有场景的效率或效果等&#xff1b;另一方面…

在线医疗诊断平台开发教程大纲 (Java 后端,Vue 前端)—实践篇-02

第三步:创建实体类和 Mapper 文件 现在我们已经设计好了数据库表结构,接下来要使用 MyBatis 将这些表映射到 Java 对象,以便在代码中进行操作。 1. 创建实体类 在 src/main/java/<your_package>/entity 目录下 (如果没有该目录,请手动创建),创建与数据库表对应的实…

基于蜉蝣优化的聚类算法(MATLAB)

优化问题广泛存在于人们的日常生活和工程领域&#xff0c;其解决如何寻找使目标值达到最优的可行解的问题。伴随着科技发展&#xff0c;优化问题在生产调度、神经网络训练、图像处理、能源系统等领域起到举足轻重的作用&#xff0c;有助于提高系统效率。优化问题依据不同标准可…

探索Spring Boot:简化Java开发的新纪元

1. 引言 在Java开发的世界里,Spring Boot无疑是一颗璀璨的明星。自诞生以来,它以简化配置、快速开发、内嵌服务器等诸多优点迅速赢得了广大开发者的青睐。本篇博客将深入探讨Spring Boot的核心概念、特点以及其在现代Java开发中的优势,并通过详细的代码示例和实践经验,展示…