当前位置: 首页 > news >正文

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)数据模型;以及用可写键路径彻底摆脱“引用循环”,让简化代码“一蹴而就”。

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

相关文章:

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

概览 小伙伴们可能不知道&#xff1a;在 Swift 语言中隐藏着大量看似“其貌不扬”实则却让秃头码农们“高世骇俗”&#xff0c;堪称卧虎藏龙的各种秘技。 其中&#xff0c;有一枚“不起眼”的小家伙称之为键路径&#xff08;Key Paths&#xff09;。如若将其善加利用&#xff…...

(十二)纹理和采样

纹理 在绘制三角形的过程中&#xff0c;将图片贴到三角形上进行显示的过程&#xff0c;就是纹理贴图的过程 uv坐标 如果如果图片尺寸和实际贴图尺寸不一致&#xff0c;就会导致像素不够用了的问题 纹理与采样 纹理对象(Texture)&#xff1a;在GPU端&#xff0c;用来以一…...

QT创建地理信息shp文件编辑器shp_editor

空闲之余创建一个简单的矢量shp文件编辑器&#xff0c;加深对shp文件的理解。 一、启动程序 二、打开shp文件 三、显示shp文件的几何图形 四、双击右边表格中的feature&#xff0c;主窗体显示选中feature的各个节点。 五、鼠标在主窗体中选中feature的节点&#xff0c;按鼠标左…...

解析Kotlin中扩展函数与扩展属性【笔记摘要】

1.扩展函数 1.1 作用域&#xff1a;扩展函数写的位置不同&#xff0c;作用域就也不同 扩展函数可以写成顶层函数&#xff08;Top-level Function&#xff09;&#xff0c;此时它只属于它所在的 package。这样你就能在任何类里使用它&#xff1a; package com.rengwuxianfun …...

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

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

STM32入门笔记(03): ADC(SPL库函数版)(2)

A/D转换的常用技术有逐次逼近式、双积分式、并行式和跟踪比较式等。目前用的较多的是前3种。 A/D转换器的主要技术指标 转换时间 分辨率 例如&#xff0c;8位A/D转换器的数字输出量的变化范围为0&#xff5e;255&#xff0c;当输入电压的满刻度为5V时&#xff0c;数字量每变化…...

2024年7月2日 (周二) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 卸载工具 HiBitUninstaller: Windows上的软件卸载工具 经典名作30周年新篇《恐怖惊魂夜…...

如何使用Spring Boot Profiles进行环境配置管理

如何使用Spring Boot Profiles进行环境配置管理 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨如何利用Spring Boot Profiles来管理不同环境…...

Java错题归纳(二)

1、若有如下接口A的定义&#xff0c;下列哪些类下确实现了该接口&#xff1a;C interface A { void method1(int i); void method2(int j); } A class B implements A{ void method1( ) { } void method2( ) { } } B class B implements A { void method1(int i ) { }…...

Grafana面试题精选和参考答案

目录 Grafana是什么以及它的主要应用场景 Grafana支持的数据源 Grafana的体系结构及主要组件 Grafana如何实现数据的可视化和监控 Grafana支持的图表类型 如何在Grafana中创建和编辑仪表盘 Grafana的查询编辑器功能 Grafana支持的认证方式 Grafana的性能调优建议 Gra…...

Node版本管理工具 fnm 安装使用

fnm 是一个基于 Rust 开发的 Node 版本管理工具&#xff0c;它的目标是提供一个快速、简单且可靠的方式来管理 Node.js 的不同版本。同时&#xff0c;它是跨平台的&#xff0c;支持 macOS、Linux、Windows。&#x1f680; Fast and simple Node.js version manager, built in R…...

vector模拟实现【C++】

文章目录 全部的实现代码放在了文章末尾准备工作包含头文件定义命名空间和类类的成员变量 迭代器迭代器获取函数 构造函数默认构造使用n个值构造迭代器区间构造解决迭代器区间构造和用n个值构造的冲突拷贝构造 析构函数swap【交换函数】赋值运算符重载emptysize和capacityopera…...

《每天5分钟用Flask搭建一个管理系统》第11章:测试与部署

第11章&#xff1a;测试与部署 11.1 测试的重要性 测试是确保应用质量和可靠性的关键步骤。它帮助开发者发现和修复错误&#xff0c;验证功能按预期工作。 11.2 Flask测试客户端的使用 Flask提供了一个测试客户端&#xff0c;可以在开发过程中模拟请求并测试应用的响应。 …...

Landsat数据从Collection1更改为Collection2

目录 问题解决 问题 需要注意!您使用的是废弃的陆地卫星数据集。为确保功能持续&#xff0c;请在2024年7月1日前更新。 在使用一些以前的代码时会遇到报错&#xff0c;因为代码里面用的是老的数据集 解决 对于地表反射率SR&#xff0c;需要在name中&#xff0c;将C01换为C02&…...

《每天5分钟用Flask搭建一个管理系统》第12章:安全性

第12章&#xff1a;安全性 12.1 Web应用的安全威胁 Web应用面临的安全威胁包括但不限于跨站脚本攻击&#xff08;XSS&#xff09;、SQL注入、跨站请求伪造&#xff08;CSRF&#xff09;、不安全的直接对象引用&#xff08;IDOR&#xff09;等。 12.2 Flask-Talisman扩展的使…...

Unity之创建与导出PDF

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之创建与导出PDF TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; 助力快速…...

【Android面试八股文】优化View层次过深问题,选择哪个布局比较好?

优化深层次View层次结构的问题&#xff0c;选择合适的布局方式是至关重要的。以下是几点建议&#xff1a; 使用ConstraintLayout&#xff1a;ConstraintLayout是Android开发中推荐的布局&#xff0c;能够有效减少嵌套&#xff0c;提高布局性能。相比RelativeLayout&#xff0c;…...

什么是带有 API 网关的代理?

带有 API 网关的代理服务显著提升了用户体验和性能。特别是对于那些使用需要频繁创建和轮换代理的工具的用户来说&#xff0c;使用 API 可以节省大量时间并提高效率。 了解 API API&#xff0c;即应用程序编程接口&#xff0c;是服务提供商和用户之间的连接网关。通过 API 连接…...

sql拉链表

1、定义&#xff1a;维护历史状态以及最新数据的一种表 2、使用场景 1、有一些表的数据量很大&#xff0c;比如一张用户表&#xff0c;大约1亿条记录&#xff0c;50个字段&#xff0c;这种表 2.表中的部分字段会被update更新操作&#xff0c;如用户联系方式&#xff0c;产品的…...

STM32CubeMX实现矩阵按键(HAL库实现)

功能描述&#xff1a; 实现矩阵按键验证&#xff0c;将矩阵按键的按键值&#xff0c;通过串口显示&#xff0c;便于后面使用。 实物图 原理图&#xff1a; 编程原理&#xff1a; 原理很简单&#xff0c;就是通过循环设置引脚为低电平&#xff0c;另外引脚扫描读取电平值&…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...