前言由于swift被设计为编译时安全和静态类型,它缺乏我们在运行时语言中经常拥有的那种动态特性,例如Object-C,Ruby,和JavaScript。例如,在Objective-C中,我们可以轻松动态地访问对象的任意属性和方法——甚至可以在运行时交换它们的实现。虽然缺乏动态性是Swift如此强大的一个重要原因——它帮助我们编写更可预测的代码和编写代码时更高的准确性,但有时,能够编写具有动态特性的代码是非常有用的。有用。值得庆幸的是,Swift继续获得越来越多的动态特性,同时仍将重点放在类型安全代码上。其中一项功能是KeyPath。本周,让我们来看看KeyPath在Swift中的工作原理,以及我们可以做哪些很酷、有用的事情。底层键路径基本上让我们将任何实例属性称为单独的值。因此,它们可以通过表达式传递,并使一段代码能够在实际上不知道的情况下获取或设置属性。KeyPaths有三种主要变体:KeyPath:提供对属性的只读访问。WritableKeyPath:提供对具有值语义的可变属性的读写访问(因此所讨论的实例也需要可变才能允许写入)。ReferenceWritableKeyPath:只能与引用类型(例如类的实例)一起使用,并提供对任何可变属性的读写访问。还有其他类型的关键路径可以减少内部代码重复和帮助类型擦除,但我们将重点关注本文中的主要路径。让我们深入了解一下关键路径是如何使用的,以及是什么让它们变得有趣和潜在强大。函数表达式假设我们正在构建一个让用户从网络上阅读文章的应用程序,并且我们有一个Article模型代表这样一篇文章,它看起来像这样:structArticle{letid:UUIDletsource:URLlettitle:Stringletbody:String}每当我们使用一个这些模型的数组,我们想从每个模型中提取一个数据来形成一个新的数组——例如在以下两个示例中,我们收集所有ID和所有文章的来源:letarticleIDs=articles。map{$0.id}letarticleSources=articles.map{$0.source}虽然上面的内容完全有效,因为我们只对从每个实例中提取单个值感兴趣,但我们并不真正需要闭包的全部功能,所以使用关键路径可能是一个不错的选择。我们将首先扩展Sequence以添加map的重载,它采用关键路径而不是闭包。由于我们只对这个用例的只读属性访问感兴趣,所以我们将使用标准的KeyPath,并且为了实际执行数据提取,我们将使用具有给定键路径的子键作为参数,如下所示:extensionSequence{funcmap(_keyPath:KeyPath)->[T]{returnmap{$0[keyPath:keyPath]}}}注意:如果你使用的是Swift5.2或更高版本,以上扩展不再必需的,因为键路径现在可以自动转换为函数。通过上述扩展,我们现在可以使用非常简单的语法从任何序列中的每个元素中提取单个值,从而可以转换我们之前的示例:letarticleIDs=articles.map(\.id)letarticleSources=articles.map(\.source)这很酷,但是,当关键路径真正开始发挥作用时,它们用于形成稍微复杂的表达式,例如在对一系列值进行排序时。标准库能够自动对包含Sortable元素的任何序列进行排序,但对于所有其他类型,我们必须提供自己的排序闭包。但是,使用键路径,我们可以轻松地添加对通过基于Comparable的键patsh对任何序列进行排序的支持。就像以前一样,我们将向Sequence协议添加一个扩展,将给定的键路径转换为排序的表达式闭包:extensionSequence{funcsorted(bykeyPath:KeyPath)->[Element]{returnsorted{a,binreturna[keyPath:keyPath]{lettitleKeyPath:KeyPathletsubtitleKeyPath:KeyPathletimageKeyPath:KeyPathfuncconfigure(_cell:UITableViewCell,formodel:Model){cell.textLabel?.text=model[keyPath:titleKeyPath]单元格。detailTextLabel?.text=model[keyPath:subtitleKeyPath]cell.imageView?.image=model[keyPath:imageKeyPath]}}上面实现的优雅部分是我们现在可以为每个模型定制我们的CellConfigurator,使用相同的轻量级键路径语法如下:letsongCellConfigurator=CellConfigurator(titleKeyPath:\.name,subtitleKeyPath:\.artistName,imageKeyPath:\.albumArtwork)letplaylistCellConfigurator=CellConfigurator(titleKeyPath:\.title,subtitleKeyPath:\.authorName,imageKeyPath:\.artwork)就像操作一样标准库中map、sorted等函数的迭代,我们以前都是用闭包来实现CellConfigurator。然而,有了关键路径,我们可以使用非常好的语法来完成它——我们不需要任何自定义操作来处理模型实例——使它们更简单,更有说服力。转换为函数到目前为止,我们只使用键路径来读取值——现在让我们看看如何使用它们来动态写入值。在很多不同的代码中,我们经常可以看到一些像下面代码这样的例子——我们使用这段代码来加载一系列的项目,然后在ListViewController中渲染它们,然后当加载操作完成时,我们将简单地分配加载的项目到视图控制器中的属性。classListViewController{privatevaritems=[Item](){didSet{render()}}funcloadItems(){loader.load{[weakself]itemsinself?.items=items}}}看看key路径赋值能不能做出上面的语法更简单,可以去除我们经常使用的weakself语法(如果忘记在self的引用前加上weak关键字,就会产生循环引用)。由于我们上面所做的只是获取传递给我们的闭包的值并将其分配给视图控制器中的属性-如果我们实际上可以将属性的setter作为函数传递,那不是很酷吗?这样我们就可以简单地将函数作为完成闭包传递给我们的加载方法,一切都会正常执行。为了实现这个目标,首先我们定义一个将任何可写对象转换为闭包的函数,然后为键路径设置属性值。为此,我们将使用ReferenceWritableKeyPath类型,因为我们只想将其限制为引用类型(否则,我们只会更改本地属性的值)。给定一个对象,并为这个对象设置一个键路径,我们会自动将对象捕获为弱引用类型,一旦我们的函数被调用,我们就会为匹配键路径的属性赋值。像这样:funcsetter(forobject:Object,keyPath:ReferenceWritableKeyPath