作者:MetaSky,万能创造者,bilibili Up 主,频道:聊有料

审核: Edmond, CocoaPods 历险记 作者,Swift & 长跑爱好者。

文章大纲

  • 前语
  • 回忆
    • 语法糖
    • 控件运用
    • 状况办理
    • 烘托流程
    • 概念差异
  • 介绍
  • 视图标识 (View Identity)
    • 界说 Identity
    • 声明式 Identity
    • 结构性 Identity
    • 危险的 AnyView
  • 生命周期 (LifeTime)
    • Lifetime 与 Identity 的联络
    • Lifetime 与 State 的联络
    • 安稳的 Identity
  • 依靠联络处理 (Dependencies)
    • 依靠联络图
    • 改善 Identity
      • 安稳性
      • 仅有性
      • 去分支
  • 总结

前语

嗨,朋友,还记得 2019 年 WWDC,苹果推出 SwiftUI 的瞬间吗?回想起来,就像在昨天,那是一个多么令人兴奋的时间啊。两年过去了,去用 SwiftUI 完成你的构思吧,对,便是现在。为什么这么肯定呢?让咱们从以下四个方面来剖析。

  • 历时三年,SwiftUI 的根本语法、控件、生态现已老练,闻名开源库根本都已适配 SwiftUI;能够在 UIKit 上自界说的控件,在 SwiftUI 上能够以更简略的办法完成
  • 苹果官方自己大力推广运用,现在苹果自家的气候、地图、相册、Apple Pay、便利指令等许多应用中,都能够看到 SwiftUI 的身影
  • 在 WWDC 2021 Session 里许多非 SwiftUI 方面的技能,都会以 SwiftUI 为示例来解说 API 的运用办法,也便是说 SwiftUI 成为了展现其他技能的根底
  • iOS15 推出后,项目能够最低支撑到 iOS 13 以上,结合 HostingController 和 HostingView 在现有的代码中运用 SwiftUI,岂不快哉

一切这些迹象都标明 SwiftUI 现已不是一个玩具,它彻底能够做出各种漂亮的界面和交互,无论是在功能仍是易用性上都没的说。

SwiftUI 能有今日的成果,阐明苹果工程师在它上面下了大力气,背面肯定经历了无数次的改善和优化,这也足以体现出 SwiftUI 在整个苹果技能生态中的重要性。

本篇文章是依据 Session 10022 – Demystify SwiftUI 编撰,Session 的演讲者是 Matt、Luca、Raj 三位大神。

回忆

本年同样是我带着咱们一起来探究 SwiftUI 内部的奥妙,为了让文章通俗易懂,让咱们先从语法糖、控件运用、状况办理、烘托流程四个方面来对 SwiftUI 有个全体的把控,假如想更连贯的了解 SwiftUI 根底知识和内部原理。引荐阅览之前由我操刀编撰的别的两篇文章 SwiftUI 数据流、SwiftUI 编程攻略 。

【老司机精选】SwiftUI 技术内幕

语法糖

简略的语法是一个声明式 UI 结构的柱石,让 SwiftUI 能够像写 HTML 相同简略的要害便是以下这些 Swift 新语法特性

SwiftUI Syntax

  • ResultBuilder
  • ViewBuilder
  • Trailing Closure
  • Opaque Type
  • Inline

State Syntax

  • PropertyWrapper
  • KeyPath
  • DynamicMemberLookup

控件运用

SwiftUI 为咱们供给了各种控件,多到看起来可能会有点蒙,下面这个分类希望能帮到你,根本上能够把一切的控件都包括在这些分类中,假如你有 SwiftUI 经历,无妨能够看着这些分类去想想那些对应的控件是怎样运用的

  • 根底控件
  • 列表控件
  • 布局容器
  • 动画和图形
  • 润饰器
  • 事件和手势
  • 集成现有结构

状况办理

一切声明式的 UI 结构,都是用数据来驱动,那么这些数据声明办法,便是在开发者和结构之间树立桥梁,非常重要。SwiftUI 官方给出了以下数据声明办法,它们全都是经过 PropertyWrapper 来包装运用。看到它们,咱们的榜首反应是不知道怎样挑选它们,这儿有个口诀:状况自己管 绑定要双向 办理靠目标 改变才监听 来协助你战胜它们。

  • @State
  • @Binding
  • @Environment
  • @StateObject
  • @ObservableObject
  • @EnvironmentObject
  • @Published

烘托流程

讲了这么多概念,那 SwiftUI 内部到底是怎样运作的呢?下面这张图能协助你更好的了解它内部的原理。

【老司机精选】SwiftUI 技术内幕

首先一切的 SwiftUI 控件都是一个结构体,实例是值类型,它们会遵从 View 协议,完成 body 核算特点;这个 body 核算特点内部所描绘的便是你想要的视图结构的姿态。所以每个 body 得到的 some View 都会映射到 SwiftUI 内部的一个 RenderNode,RenderNode 也会持有在自界说 View 上界说的各种状况,为这些状况分配内存空间存储数据,一起给这些状况的增加特点监听,一旦状况特点发生改变,就从头树立 some View 到 RednerNode 的映射联络。后台的烘托引擎 (CoreGraphics, Metal) 会经过 RenderNode 比照 some View 的改变,在 RunLoop 的加持下,将改变的部分制作出来,终究呈现给用户。

尽管上面的流程是这姿态的,但在之前 SwiftUI 官方仅仅告知你怎样把数据声明为 SwiftUI 可感知的状况,触发界面制作。并没有明晰的阐明以下四个问题:

  1. SwiftUI View 和 RenderNode 之间是依照什么联络来映射的?
  2. SwiftUI View 和 RenderNode 生命周期是否共同,存在什么联络?
  3. SwiftUI View 从头实例化后,State 是怎样被保持住的?
  4. 状况发生改变后,SwiftUI 是怎样找到相应的 View 和 RenderNode 来进行操作的?

这篇文章经过通俗易懂的比如,来答复上面四个问题,并协助你养成杰出的 SwiftUI 编程习惯。

概念差异

在下面的文章中请留意差异如下两个概念:

  • SwiftUI 控件View 都是结构体,是值类型,代表的是开发者用 DSL 描绘的界面布局和层级
  • 视图界面元素都是类,是引用类型,指的是烘托节点或实在显现的 UI 界面

介绍

众所周知,SwiftUI 是一个声明式的 UI 结构,也便是说开发者能够用 Swift 编程言语来描绘应用界面的姿态,SwiftUI 内部来处理视图烘托,终究将界面展现给用户。

大多数状况下,SwiftUI 开发确实简略、省事、Bug 少,开发者用起来爽的不要不要的。但一旦呈现不行预期的行为,就抓瞎了,这时分需求了解 SwiftUI 的技能内情,才能更好的处理这些异常状况。

【老司机精选】SwiftUI 技术内幕

那么 SwiftUI 内部是怎样处理开发者编写的描绘性代码的呢?其内部有三个中心概念来支撑:

  • 视图标识 (Identity) – 标识在应用程序的屡次更新进程中视图元素,决议是否从头生成视图元素
  • 生命周期 (Lifetime) – 盯梢视图和数据状况随时间改变的进程,依据开发者描绘来处理视图怎样更新
  • 依靠联络 (Dependencies) – 对数据状况进行监听,决议视图何时需求更新

这三个中心概念协助 SwiftUI 解决什么需求改变,怎样改变,以及何时改变的问题,终究烘托出相应的用户界面。

接下来,让咱们更深化地讨论这三个概念。

视图标识 (View Identity)

界说 Identity

【老司机精选】SwiftUI 技术内幕

上图中这两只狗狗,到底是不是同一个呢?咱们好像无法精确地给出答案。为什么呢?由于咱们缺少一些要害信息,那便是 Identity。

所以当 SwiftUI 处理你的界面描绘时,它也需求 Identity 这个要害信息差异视图是否是同一个。

【老司机精选】SwiftUI 技术内幕

让咱们来看下上面这个 Good Dog, Bad Dog 的小应用,你能够点击屏幕上的任何方位来切换狗狗的状况。可是咱们从技能层面剖析,上面的界面能够有两种 SwiftUI 的描绘办法:

  1. 自界说两个彻底不同的 SwiftUI View,依据当前狗狗的状况去做逻辑判别描绘
  2. 把上面的界面描绘成一个 SwiftUI 自界说 View,在差异展现的当地,用不同的颜色来差异

这两种 SwiftUI 的描绘办法,会让视图从一种状况过渡到另一种状况的办法天壤之别:

  • 依照榜首种办法,由于是彻底不同的视图,就意味着上面狗爪子的图标应该独立履行过渡动画,终究看起来只要淡入和淡出的作用
  • 依照第二种办法,SwiftUI 内部认为它是同一个视图,这就意味着在过渡期间,狗爪子图标会履行在屏幕上滑动的动画作用

能够看出 SwiftUI 在处理过渡动画的时分,会依据不同状况下的 View 是怎样衔接的来进行处理,而决议 View 衔接办法的要害便是 View Identity:

  • 同享 Identity 的 View 代表的是同一个 UI 界面元素,仅仅处在不同状况下而已 (Same identity = Same element)
  • 代表不同 UI 界面元素的 View,它的 Identity 也总是不同 (Different identities = Distinct elements)

Identity 已然这么重要,那么开发者是怎样用代码来界说的呢?在 SwiftUI 中分两种办法来界说 Identity:

  • 声明式 Identity,一般是在 View 上增加一个 id(_:) 润饰器或许在数据驱动列表控件中显现声明 Identifier,如 ForEach、List
  • 结构性 Identity,是 SwiftUI 依据 View 的类型和层级结构来动态辨认,尽管这种 Identity 不需求开发者指定,但也需求开发者明晰的将 View 的层级结构描绘出来,便利 SwiftUI 内部辨认。

声明式 Identity

【老司机精选】SwiftUI 技术内幕

就像上面这两只狗狗,仅经过图片,很难判别这是不是同一只狗狗,但假如咱们能用名字来标识它们。就很简单得出结论。像这样给狗狗起名字来标识它们的办法,便是在显式声明 Identity。

需求留意的是,声明式 Identity 是非常强大且灵敏的。咱们在之前 AppKit 或 UIKit 中编写界面的办法,其实便是采用的显式声明 Identity 的办法。

由于 UIView 和 NSView 都是类,引用类型,所以它们的实例其实是一个指针,这个指针指向了一块内存空间。其间指针所代表的内存地址便是一种显式声明的 Identity。

咱们能够经过视图的指针来标识每个视图,假如多个视图指针,都同享同一块内存空间,那么它们其实是同一个视图,如下图所示:

【老司机精选】SwiftUI 技术内幕

可是,SwiftUI 中的 View 都是结构体, 值类型,没有指针的概念,如下图,那 SwiftUI 怎样来仅有标识一个 View 的呢?

【老司机精选】SwiftUI 技术内幕

其实,SwiftUI 是用别的一种形式来显式标识 View。经过下面的比如能更好的了解,例如在这个救援犬列表里,用 dogTagID KeyPath 获取相应特点,在参数里指定到 id 上,便是在显式声明 View 的 Identity。这样就能标识出每条数据对应的展现视图。一旦列表数据发生改变, SwiftUI 能够依据这些 ID 来判别,哪些视图需求新生成,哪些视图重复运用,只需求履行动画。

【老司机精选】SwiftUI 技术内幕

来看一个更高档的列子,如下图。在这儿,咱们运用一个 ScrollViewReader,点击底部的一个按钮能够跳到视图的顶部,这儿便是用 id(_:) 润饰器来显式声明顶部 HeaderView 的 Identity。然后咱们能够将该 Identity 传递给 ScrollViewProxy 的 scrollTo(_:) 办法,然后完成相应的翻滚作用。

从代码中能够看出,ScrollViewReader、ScrollView 和 Button 都没有显式指定 Identity。所以 SwiftUI 期望的是在需求的当地显式声明 Identity 就能够,并不需求为一切的 View 显式声明。

【老司机精选】SwiftUI 技术内幕

结构性 Identity

如上所述,不显式声明 Identity,这并不意味着这些 View 根本没有 Identity,也便是说每个 View 都有一个 Identity,即便它不是显式声明出来的。在这种状况下 SwiftUI 内部会对没有显式 Identity 的 View 依据它的描绘层级结构生成一种隐式的 Identity,就叫做结构性 Identity。

【老司机精选】SwiftUI 技术内幕

如上图,假设咱们有两只相似的狗狗,但咱们不知道它们的名字,咱们仍然还要标识出它们。这时分能够经过它们坐的方位来标识,如 左边的狗右边的狗

像这种运用排列方位的不同,来差异它们的办法便是所谓的 结构性 Identity

SwiftUI 几乎在一切当地都采用了这种结构化 Identity 的办法来标识 View。一个典型的比如便是在 SwiftUI 中 运用 if else 条件判别的时分,条件句子的结构使得 SwiftUI 能够明晰的辨认每个 View,如下图,榜首个 AdoptionDirectory 只在条件为 True 的时分显现,第二个 DogList 只在条件为 False 的显现。

【老司机精选】SwiftUI 技术内幕

可是 SwiftUI body 核算特点需求一个明晰共同的回来类型,但 if else 条件判别使得回来类型不共同了,会引起编译失败。这时分 SwiftUI 引入了一个的黑魔法 – ViewBuilder (默认是附加在 body 核算特点上的,不需求开发者独自指定)。ViewBuilder 协助 SwiftUI 把各种条件判别,封装成 _ConditionalContent 的数据结构。但为了差异在不同分支下的类型不同,用泛型来进行了差异,这样即保证了回来数据的共同性,又保证了 SwiftUI 内部能够经过泛型辨认出不同分支下结构性 Identity。

如下图,咱们再回到 Good Dog, Bad Dog 的小应用,假如运用分支句子来描绘 SwiftUI 的 View,就会在不同的条件下生成不同的结构性 Identity。在 SwiftUI 内部会生成不同的视图元素实例,它们是不连续的,这也就解释了在不同状况下界面切换的时分,为什么只会有淡入淡出的过渡作用。

【老司机精选】SwiftUI 技术内幕

而用别的一种办法来描绘,如下图,咱们只用一个 PawView 自界说View,在这个自界说的 View 的 Modifier 上运用三目运算的办法来动态改变需求改变部分的数值,当在不同状况之间发生界面切换的时分,由于始终是一个视图元素,所以就会履行滑润的滑动动画。

其实,假如你回到 UIKit 中了解,也是相同的,咱们在 UIView 上履行动画的时分,一般也是在同一个 UIView 的实例里去动态改变它的特点去修正款式, 才会有那种滑润过渡的作用;相反,假如尽管是同一个类型的 UIView,可是对应的是不同的实例,去做那种滑润的过渡作用,也是很难完成的。

【老司机精选】SwiftUI 技术内幕

综上所述,在运用结构性 Identity 的时分,第二种描绘 View 的办法是更好的挑选。应该尽量防止切换 Identity,这样做会给动画和功能都带来杰出的作用,也有利于保持视图的生命周期和数据状况。

危险的 AnyView

说起 AnyView ,这家伙绝对是 Identity 的克星。

【老司机精选】SwiftUI 技术内幕

上图是一个运用 AnyView 的示例代码。在这个自界说 View 中,为了保证终究回来一个明晰共同的数据类型,每个分支都用一个 AnyView 包裹起来。由于 AnyView 隐藏了所包装视图的类型,让 SwiftUI 无法在条件判别中辨认出结构性 Identity,在 SwiftUI 眼里,它看到的都是一些擦除类型的 AnyView,更要命的是,这段代码阅览起来特别困难。

那么,接下来让咱们用正确的办法来重构这段代码:

  • 榜首步:消除 AnyView 包裹,把内部具体的 View 类型暴露出来
  • 第二步:去掉 一切的 return 要害字
  • 第三步:在办法上增加 @ViewBuilder 标识,保证终究回来的是一个明晰的类型,编译经过
  • 第四步:由于咱们仅仅在 dog 的 breed 状况之间来回判别,那么把 if else 改为 switch case 会更适宜

重构后,终究代码和 View 层级结构如下图:

【老司机精选】SwiftUI 技术内幕

一般状况下,仍是尽量防止运用 AnyView,由于 AnyView 有如下缺点:

  • 代码难于阅览
  • 由于擦除了一切的 View 类型,无法在编译的进程中给出相应的提示
  • 可能会导致不必要的功能丢失

生命周期 (Lifetime)

Lifetime 与 Identity 的联络

上面咱们了解了 SwiftUI 怎样给 View 增加 Identity 。下面咱们来看下 Identity 与视图的生命周期的联络。

【老司机精选】SwiftUI 技术内幕

如上图,这儿有个叫 Theseus 的小猫。他在一天中可能有各种不同的状况,一会装可爱,一会睡觉,一会发火,可是无论处于何种状况,他都是那只叫 Theseus 的小猫。

视图在整个生命周期内有各种不同的状况,每个状况在 SwiftUI 中由不同的 View 实例(值类型)来描绘,而 Identity 将这些不同状况下的 View 值跟着时间的推移相关起来,它们都对应着同一个视图元素。这便是 Identity 与视图生命周期树立联络的实质。

【老司机精选】SwiftUI 技术内幕

让咱们用上面的代码来更明晰的了解这一点,这儿咱们有一个简略的自界说 View – PurrDecibelView,用来显现猫叫声的强度。一开端的时分 SwiftUI 调用 body 核算特点,获取到叫声为 25 的 View,可是忽然小猫饿了,希望获得更多的重视,叫声变大为50,这时分 SwiftUI 监听到叫声这个状况的改变,从头调用 body 核算特点,获取到一个全新的 View,这两个 View 是彻底天壤之别的两个值。SwiftUI 会在后台对这两个值进行比照并比照出哪些部分发生了改变。得出比照成果后,告知烘托视图履行改变部分的烘托操作,一起,用完的 View 值也会被毁掉。

这儿非常重要的一点便是 View 的值跟 Identity 生命周期是不同的。值类型的 View 生命周期是非常时间短的。开发者要控制好的其实是它们的 Identity。也便是说,跟着时间的推移, SwiftUI 创立许多新的 View 用来描绘视图当前状况下的显现办法,可是 SwiftUI 内部仅仅拿这些 View 来进行款式和布局的比照,用完了这些 View 值就会毁掉,其内部用 Identity 仅有标识的那个视图(RenderNode)会一向在内存中,而且一向都是同一个。可是一旦 Identity 发生改变,内部的视图元素生命周期也会结束。

所以,如下图,咱们常常用到的生命周期办法 onAppear 和 onDisappear,其实是在视图显现和消失的时分触发,而不是 View 创立和毁掉的时分触发。

【老司机精选】SwiftUI 技术内幕

所以终究咱们得出如下公式来论述 View,LifeTime,Identity 三者之间的联络:

1. View Value  View Identity
2. View(视图)'s LifeTime = duration of the Identity

Lifetime 与 State 的联络

了解了 Identity 与视图生命周期之间的联络,也能够协助你更好地了解 SwiftUI 怎样保持数据状况。

提到保持数据状况,那肯定要用到 State 和 StateObject。这两个状况办理工具能够保证在不同的 View 实例被创立的时分,封装的数据能够一向保持在内存中,相当于一种内存记忆。可是你去看它们的界说会发现它们都是结构体。按理说,在每次创立新的 View 实例后,应该就毁掉从头生成了,那咋保持数据的呀?其实它们内部都会有一个 Storage 类,用来存储它们所润饰的数据。当一个视图依据 Identity 榜首次创立的时分,SwiftUI 在内部为 State 和 StateObject 的 Storage 分配相应的内存空间,用来保存状况的初始值。留意这儿的 Storage 跟 Identity 是对应的,生命周期也是共同的

如下图的 CatRecorder 自界说 View,每次的 title 发生改变,由于他被 @State 润饰,SwiftUI 内部会在内存中保存这个数据,而且监听他的改变,一旦发生改变,就调用 body,从头核算。

【老司机精选】SwiftUI 技术内幕

下面,让咱们来看一下在有分支的状况下,视图生命周期和数据状况之间的联络。

【老司机精选】SwiftUI 技术内幕

如上图代码,分支里的两个 CatRecorder 由于结构性 Identity 不同,所以它们被 SwiftUI 视为是两个不同的视图。之前说过这样会影响动画作用,其实也会影响它们内部数据状况的保持。

比方说,榜首次进入的是 True 分支,SwiftUI 会为 CatRecorder 生成一个新的视图,并为数据分配内存空间,以存储状况的初始值。当 CatRecorder 内部状况发生改变时,只要都是在 True 分支下,由于 Identity 没变,所以仍是同一个视图,所以状况也会连续性的改变,不会有数据丢失的状况。可是一旦 dayTime 发生改变,进入了 False 分支,SwiftUI 发现 Identity 发生了改变,会生成新的视图和与之对应状况的内存空间,这时分新的 CatRecorder 内部的一切状况都是初始值。True 分支下的视图和对应的状况接下来也会被开释。假如咱们再切回到 True 分支,之前 True 分支的状况也回不来了,由于相较于上次的 View 类型,这又是一个全新的 Identity,会从头创立视图和数据状况存储空间。所以,终究分支切换后,在界面上有时分会发现记载的小猫状况忽然丢失了。

所以能够得出的结论是:View Identity 一旦改变,视图内部对应的数据状况也会被从头替换。也便是说:

State's Lifetime = 视图's Lifetime != View's Lifetime

安稳的 Identity

保证 Identity 安稳,这一点非常重要,尤其是在运用数据驱动型的列表控件时,在下面这些控件中,往往都需求用数据的 id 来给 View 显式声明 Identity。

【老司机精选】SwiftUI 技术内幕

下面两张图是两种不同的 ForEach 的用法。其间榜首种用法是一个常数的 Range,SwiftUI 能够直接用 Range 的值来为视图生成 Identity,以保证在视图的整个生命周期内 Identity 是安稳的。但当运用一个动态的 Range 时,会导致声明式的 Identity 数值是不行预期的,Identity 一旦切换,视图都会从头生成,这样就会呈现功能问题。 所以在 Xcode 12 的时分,检查到这种运用办法会编译报错,而在新版本的 Xcode 13 这将变为一个警告 (beta 版本好像不生效)。

【老司机精选】SwiftUI 技术内幕

【老司机精选】SwiftUI 技术内幕

【老司机精选】SwiftUI 技术内幕

下图中,咱们用一个动态的集合作为 ForEach 的初始化参数。这样用就需求一起配上对应数据结构的 KeyPath,便利获取到数据的相应特点来显式声明为 Identity。留意这个特点有必要遵从 Hashable 协议,来保证 Identity 的仅有安稳性。

【老司机精选】SwiftUI 技术内幕

在 Swift 标准库有个 Identifiable 协议来协助开发者保证 Identity 安稳。SwiftUI 也充分运用了这个协议,使得开发者只需求供给 KeyPath,它内部经过 Identifiable 协议能够动态的访问到相应的特点,然后生成安稳的 View Identity。

【老司机精选】SwiftUI 技术内幕

如上图,假如仔细看下 ForEach 控件初始化函数的界说,能够看出 SwiftUI 充分运用了 Swift 类型体系的特性来束缚 API 运用体会:

  • 经过这个界说,能一眼看出 ForEach 声明晰一个数据集合和一个视图集合之间的联络
  • 将集合中的元素限制为有必要遵从 Identifiable 协议,意图是为了保证集合元素能够供给一个安稳的 Identity,以便 SwiftUI 能够在视图的整个生命周期内盯梢数据。

所以,保证 Identity 的安稳性,关于开发者来说是非常重要的。由于他会影响到视图和与之对应数据的生命周期。

依靠联络处理 (Dependencies)

依靠联络图

【老司机精选】SwiftUI 技术内幕

接下来,让咱们看下上面的代码,这个自界说的 View 有两个特点 dog 和 treat,它们都能够了解为视图的依靠联络。依靠联络便是视图更新的入口。当依靠联络发生改变时,会从头调用 View 的 body,获取整个 View 的层级描绘信息。在这个比如中,描绘的便是一个有触发行为的按钮。他对应的视图层级结构如下:

【老司机精选】SwiftUI 技术内幕

看上面这张图的话,是一个树结构,可是有可能多个视图都依靠同一个状况。有可能某个子视图也依靠顶级视图中的状况。状况越来越复杂后,这就不再是一个树结构。从头整理,防止让衔接线之间穿插,如下图,能够看出它们之间的联络实践上是一个图结构。咱们能够称之为依靠联络图

【老司机精选】SwiftUI 技术内幕

深化的了解这个依靠联络图很重要,由于它保证了 SwiftUI 只更新那些需求从头调用 body 的 View。以最底部的依靠联络为例。假如咱们检查这个依靠联络,会发现有两个 View 依靠它,当依靠的数据状况发生改变,只要这两个 View 会被标记为无效。一起 SwiftUI 开端调用每个视图的 body 核算特点,只为标记为无效的视图发生一个新的 body 值。

【老司机精选】SwiftUI 技术内幕

正如在文章最初介绍的状况办理工具,在 SwiftUI 依靠联络的树立便是经过它们来完成的:

  • @Binding
  • @Environment
  • @State
  • @StateObject
  • @ObservableObject
  • @EnvironmentObject

改善 Identity

Identity 便是依靠联络图的魂灵。正如之前所说,Identity 用来标识一个视图,所以 SwiftUI 会依据 Identity 来高效的判别哪些视图需求更新,哪些视图需求新建,哪些视图需求毁掉。

安稳性

关于开发者来说,首先要保证的便是 Identity 的安稳性。

安稳的 Identity 会给 SwiftUI 带来如下优点:

  • 保证视图生命周期的精确性,一个视图的生命周期是由 Identity 来决议的,一个不安稳的 Identity 会导致视图生命周期意外缩短
  • 进步应用程序的功能,SwiftUI 无需在依靠联络图更新的进程中为不必要的视图和状况从头分配内存空间
  • 缩小影响依靠联络影响的规模
  • 保证数据状况不会无故丢失

在下图的比如中,每次都生成一个 UUID 和 直接用 Indices 来显式声明 Identity 都是不安稳的办法,由于它们都会跟着时间推移发生改变,不能精确地标识一个视图,终究导致的成果便是,当咱们在列表头部新刺进数据时,整个列表都会从头改写。相反,咱们假如用一个 databaseID 便是能够的,由于这个 ID 只对应一个数据,能够明晰的标识一个与该数据对应的视图。这时分咱们在头部新刺进数据,一切的动画作用都非常天然了。

【老司机精选】SwiftUI 技术内幕

【老司机精选】SwiftUI 技术内幕

【老司机精选】SwiftUI 技术内幕

仅有性

可是只保证 Identity 的安稳性仍是不行的。好的 Identity 还要保证仅有性。每个 Identity 都应该精确映射到一个单一的视图。

仅有的 Identity 会给 SwiftUI 带来如下优点:

  • 滑润的动画作用
  • 同样能够进步功能
  • 精确地的反应视图和状况之间的依靠联络

像下面的代码中运用 name 的 KeyPath 来给 View 显式声明 Identity,是不合理的,由于咱们无法保证 name 的仅有性,一旦呈现重名的状况,新的视图很有可能不会展现出来。但当把 name 换成 serialNumber,一切都正常了。

【老司机精选】SwiftUI 技术内幕

【老司机精选】SwiftUI 技术内幕

去分支

上面,咱们都是用声明式 Identity 来阐明怎样改善 Identity,接下来看看怎样改善结构性 Identity。

【老司机精选】SwiftUI 技术内幕

上面的代码,乍一看好像没什么问题。可是仔细剖析会发现,这儿有个功能问题。content 在不同的分支条件下,会发生不同的结构性 Identity,这就导致了分支切换后针对同一个 View 会生成两个不同的视图元素,也便是在内存中分配两份内存空间。这点其实是能够防止的。尽管这儿咱们很轻易的发现了这个问题,但当项目大了之后,有可能这些 ViewModifier 的代码都不在一起,所以这种问题很简单被忽视。

下面的代码,咱们把分支结构去掉,改为在 opacity 润饰器上增加三目运算的办法来动态修正透明度。由于去掉了分支结构,所以 content 只会生成单一的结构性 Identity,也就防止了不必要的内存开支,进步了功能。

【老司机精选】SwiftUI 技术内幕

像上面代码直接把透明度设置为 1,也便是跟初始状况共同,其实 SwiftUI 发现这种状况是不履行任何烘托操作的。咱们把这样的润饰器称为 “慵懒润饰器”,由于它们不影响烘托的成果。

读到这儿,你是不是跟我相同都不敢在 SwiftUI 中运用条件分支了。咱们尽量仍是不要担心,我建议想用分支的时分仍是得用,仅仅用完之后,要多考虑下这个当地用分支来描绘 View 结构的必要性,也便是要考虑当前代码的 View 到底是用来代表多个视图仍是代表同一个视图的不同状况。

假如是代表同一个视图的不同状况,那么运用一个慵懒润饰器来标识一个单一的视图,往往是更好的挑选。

在下图中还给出了一些其他的慵懒润饰器作为参考:

【老司机精选】SwiftUI 技术内幕

总结

整篇文章的主角便是 Identity。咱们介绍了 Identity 在 SwiftUI 中怎样影响动画、生命周期以及相对应的状况。一起论述了在视图更新的进程中,也需求 Identity 来协助 SwiftUI 做决议。咱们给出了许多正确运用 Identity 的范例,这将对进步 SwiftUI 应用程序的功能很有协助。

下面咱们来答复文章最初的问题:SwiftUI View 和 视图元素之间采用 Identity 相关起来,它们之间并非一一对应。在 SwiftUI 中每当状况发生改变,都会调用对应的 body 生成新的 View 值,可是否生成新的视图则彻底由 Identity 来决议。假如 Identity 共同,就会依据 Identity 去内存中查找之前创立的视图,换言之,相当于保持之前视图的生命周期,而且在内存顶用类保持住之前的数据状况,只对更改数据后,视图改变的部分进行烘托操作。假如 Identity 不共同,则会新建视图元素,一起视图所依靠的状况也会被从头分配,回到初始值。

总而言之,View Identity 对 SwiftUI 来说是至关重要的。咱们一定要时间留意 View 的 显式 Identity结构性 Identity,并进步 Identity 的安稳性,保证 Identity 的仅有性。

重视咱们

咱们是「老司机技能周报」,一个继续寻求精品 iOS 内容的技能大众号。欢迎重视。

重视有礼,重视【老司机技能周报】,回复「2021」,领取 2017/2018/2019/2020 内参

支撑作者

在这儿给咱们引荐一下 《WWDC21 内参》 这个专栏,一共有 102 篇关于 WWDC21 的内容,本文的内容也来源于此。假如对其余内容感兴趣,欢迎戳链接阅览更多 ~

WWDC 内参 系列是由老司机牵头组织的精品原创内容系列。 现已做了几年了,口碑一向不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实践开发经历、苹果文档和视频内容做二次创造。