vlambda博客
学习文章列表

活用 Swift 类型推断

关注有礼,文章末尾有粉丝抽奖福利。❤️


Session: https://developer.apple.com/videos/play/wwdc2020/10165/

概述

Swift 广泛使用类型推断为开发者提供简洁的语法,同时保证代码的安全性。在本主题中,我们将讨论类型推断的实现原理,了解如何使用 Xcode 提供的辅助信息,快速修正代码错误。

本文基于WWDC20 - Embrace Swift type inference 梳理

SwiftUI 中的类型推断

我们团队在做一款名为『Fruta』的订购冰沙应用。在右侧的预览中,你可以看到当前的冰沙列表。我想增加一个功能:用户可以输入关键字,列表页内容根据输入内容产生变化。

先来通过代码看看当前显示全部冰沙列表是如何实现的:在 SmoothieList 中,通过 smoothies 数组映射生成 SmoothieRowView 列表。

为了增加搜索功能,SmoothieList 需要新增 @State 属性 searchPhrase 来存储用户输入的字符串。

List 需要调整为 FilteredList,FilteredList的构造器有三个参数:smoothies 数组,filterBy KeyPath 过滤特定属性,isIncluded 闭包返回对应属性的包含关系。调整后的代码如下:

活用 Swift 类型推断

从调用者来看一切非常自然,然而 FilteredList 的初始化过程很大程度上依赖类型推断。让我们来看看是什么原因导致这段调用依赖类型推断,以及调用上下文如何利用类型推断提供简洁易用的的接口。

为了理解调用过程省略的细节,我们先看看 FilteredList 的声明。FilteredList 是一个通用的视图类型,它需要支持组合不同类型的数据、过滤属性以及视图,这些都是通过范型来提供的。

活用 Swift 类型推断

FilteredList 引入 Element、FilterKey、RowContent 三个范型参数,他们起到占位符的作用,在调用时被具体类型所替代。这些占位符所对应的具体类型,要么由调用者显式指定,要么通过类型推断来生成。

在本例中,Element 是数组元素数据类型的占位符,FilterKey 是元素特定属性类型的占位符,RowContent 是列表每行显示视图类型的占位符。

FilteredList 构造器的每一个参数的类型信息以及调用时的上下文,都会作为类型推断的线索之一。我们稍后会看到这一点是如何工作的,先来看看 FilteredList 构造器每个参数的类型细节:

  • data: [Element]:data 是一个 Element 类型的数组,提供视图将要映射的数据

  • key: KeyPath<Element, FilterKey>:要过滤的特定属性类型,基本类型为 Element,值类型为 FilterKey

  • isIncluded: @escaping (FilterKey) -> Bool:一个函数闭包,FilterKey 类型数据作为输入,返回 Bool 判断是否需要过滤。这个闭包需要持有,所以声明 @escaping。

  • @ViewBuilder rowContent: @escaping (Element) -> RowContent:另一个函数闭包,输入是一个元素数据 Element,返回 RowContent 视图。同样因为需要持有,声明 @escaping。@ViewBuilder 是 SwiftUI DSL 语法,可以把闭包中多个子视图自动组合为元组供父视图使用。

这就是 FilteredList 构造器完整类型信息,下面我们看看调用过程如何来进行完整的类型推断。

活用 Swift 类型推断

把构造器声明和调用过程放在一起,你可以看到调用过程非常简洁,同时它仍然为编译器提供了完整的类型信息。

活用 Swift 类型推断

如果我们在代码中显式声明所有参数类型,调用会是什么样子?我们先通过 FilteredList 构造器补充参数类型,范型对应的类型先用占位符表示。

活用 Swift 类型推断

相比之前的调用,已经啰嗦很多,这还不够,类型推断还需要补充对应占位符的类型信息,完善调用阶段全部的类型细节。

手动填入所有类型信息是一项繁杂且无聊的工作,类型推断让调用者从类型信息中解放出来,更快的编写代码。

让我们来谈谈编译器是如何把占位符转换成具体类型的。

类型推断过程

🧩 你可以把类型推断看成智力拼图游戏,类型推断的过程是就是补充一块块拼图的过程,根据某条线索填补一块拼图,又可以发现更多关于其它拼图的线索。活用 Swift 类型推断

让我们来玩一下刚才示例中的『智力拼图』。

从上下文可以明确 smoothies 的类型是 [Smoothie],这可以作为第一块拼图🧩:占位符 Element 对应 Smoothie 类型。

活用 Swift 类型推断

有了第一块拼图,我们可以吧其它 Element 都对应到 Smoothie 类型,为后续推断提供更多线索。

活用 Swift 类型推断

之前 \Element.title 类型还无从下手,现在 \Smoothie.title 明确是 String 类型。这里就找到第二块🧩:占位符 FilterKey 对应 String 类型。

活用 Swift 类型推断

再来看看 RowContent,由于闭包只返回一个子视图 SmoothieRowView,那么我们解决了最后一块拼图🧩:占位符 RowContent 对应 SmoothieRowView 类型。

至此,所有的拼图补充完成:

活用 Swift 类型推断

上图中灰色部分就是类型推断最终补充的类型信息,可以想象,如果这些内容需要开发者手动输入是一件多么繁琐的事情。在 Swift 中,这些部分都有类型推断自动完成,开发者只需关注接口调用本身。

全新的综合错误跟踪

代码从不同角度为编译器提供拼图线索,每一步补充的拼图又会提供新的线索。有时,不同的线索可能相互冲突,这就意味着代码出现了错误。我们再来看看线索冲突时编译器如何判断错误,如何提供合适的辅助信息帮助开发者定位问题。

我们把 \Smoothie.title 替换为 \Smoothie.isPopular。与 title: String 不同的是,isPopular 是 Smoothie 结构体的一个 Bool 属性。

类型推断按照原有的方式补充拼图🧩:占位符 FilterKey 对应 Bool 类型。

活用 Swift 类型推断

而在 isIncluded 闭包中,title 对应 Bool 类型,但是 Bool 类型,是没有 hasSubstring 方法的,拼图无法完成,编译器报错。

活用 Swift 类型推断

众所周知,编写代码的过程中错误不可避免,编程语言及工具必须把对错误的友好处理纳入设计之中。Swift 将综合错误跟踪集成在类型推断算法中,在类型推断过程中,编译器会记录所有错误信息,同时用启发式方法修复错误,尝试继续推断类型。

一旦推断完成,编译器将报告他收集到的所有错误。这种提示的错误,通常有快捷恢复的提示,以及带有错误注释信息及导航的面包屑。

Xcode11.4 和 Swift5.2 中,大量错误使用综合错误跟踪『Integrated error tracking』实现,而在 Xcode12 和 Swift5.3 中,所有错误都将使用这种新策略。

活用 Swift 类型推断

我们都有过面对编译器错误的沮丧时刻,综合错误处理就像结对编程的好帮手一样,帮助你梳理错误信息,快速修复错误。

活用 Swift 类型推断

在编写代码之前,我们先勾选『Behaviors-Fails-Show navigator Issues』开关,当构建失败时,它会自动切换导航到 Issues 列。

活用 Swift 类型推断

我们尝试在界面上引入 TextField 用来输入搜索文本,在构造器中 text 参数传递的是 searchPhrase。点击构建(快捷键 cmd+B),出现一个编译器错误:text 参数需要一个 Binding 类型字段,代码中传递的是 String 类型。编译器准确的提供错误信息,并且提供改写为 $searchPhrase 的快速 Fix 选项。点击 Fix,这部分代码被快速修复。

活用 Swift 类型推断

再次编译,我们得到另一个错误:Smoothie 需要实现 Identifiable 协议。🤷‍♀️,这似乎让人一头雾水,什么是 Identifiable?

活用 Swift 类型推断

留意,在左侧的导航中,有关于这个错误的附加信息。通过新的综合错误跟踪,编辑器记录类型推断过程中的错误信息,生成错误信息面包屑,帮助你快速查看相关代码。

点击 Where 'Element' = 'Smoothie',你将看到 FilteredList 的定义,Element 需要实现 Identifiable 协议。修正错误也非常简单,Smoothie 实现 Identifiable 协议,重新编译,顺利通过。

活用 Swift 类型推断

来看看我们的工作成果,同时显示全部内容和搜索Berry关键字的两份预览,二者都符合预期。

活用 Swift 类型推断

总结

活用 Swift 类型推断

通过本主题,我们了解到:

  • 编写 SwiftUI 可复用的视图时,很大程度上依赖类型推断

  • 类型推断通过源代码的蛛丝马迹巧妙的补充类型信息

  • 新的综合错误跟踪提供更友好的面包屑结构错误信息

推荐阅读

支持作者

WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。

关注有礼

我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。

福利一

关注「老司机技术周报」回复【2019】,送《WWDC2019 内参》和 《SwiftOldDriver 精选(2019)》小专栏一份。

福利二

另外, 正式上线,欢迎大家前往订购~