原文链接
如果你已经在使用SwiftUI,那么今天要学习的内容非常重要,因为今天要对SwiftUI的三个核心的基本原理窥探究竟:1. Identity 2. Lifetime(生命周期) 3. Dependencies(依赖关系),找出通用的模式,学习框架驱动的原理,并了解如何使用它们来保证APP的正确和性能。
我们已经听说了很多次:SwiftUI是一个声明式的UI framework. 意思是你在一个很高的代码层次上描述你的app想要什么,然后SwiftUI能够准确地领会你的意思,然后实现你要的目标。
大部分时间,SwiftUI运行的很好,你会感觉SwiftUI很神奇。
但有时候SwiftUI也会做出你意想不到的事情,它可能无法达到你的预期,所以了解SwiftUI背后的原理,变得很重要,这有助于SwiftUI呈现给我们想要的结果。
今天的问题是,当SwiftUI看到你的代码时,它看到了什么?答案是三件东西:identity(ID), lifetime(生命周期), and dependencies(依赖关系).
Identity
是SwiftUI在更新界面时识别相同或不同元素的方式。
LifeTime
是SwiftUI跟踪视图和数据的生存周期。
dependencies
SwiftUI如何理解你的界面何时需要更新,以及为什么需要更新。
这三个概念共同决定了SwiftUI如何决定需要改变什么、如何改变、何时改变,从而产生你在屏幕上看到的动态用户界面。
今天我们将深入的探讨这三个概念。
Identity
首先是identity, 举个例子:假如界面上有两张狗的图片,除了狗的表情不一样,两张图片基本一样;那如何判断它们是两只狗还是一只狗的两个状态,我们没办法判断,因为缺少足够的信息。
这个问题的核心是狗的"Identity", 这很重要。
这也是SwiftUI如何理解你的app的关键。
我们看一个例子:
这是一个app,我们可以叫做“Good Dog,Bad Dog”,这个app帮助我们的狗狗是否有好的行为,这个app非常简单。
我们可以通过点击屏幕的任何地方来切换good 和 bad两种状态。
所以,identity 跟我们的app有什么关系呢,我们看看两个界面上的 狗狗的爪印的图标,这两个图标是两个不同的视图还是相同的视图,只是颜色和位置不一样?这种区别实际上非常重要,因为它会影响视图从一种状态转换到另一种状态的动画方式。
假如是不同的视图,那么这两个视图的动画方式应该是独立的,无关的,比如渐入和渐出。
那假如是相同的视图呢?这意味着视图应该在转换期间滑过屏幕,然后从一个位置移动到另一个位置。
因此连接不同状态的视图是很重要的,因为这是SwiftUI理解如何在它们之间转换的方式。
这是视图背后的identity的关键问题。
视图共享相同的id,用来表示相同视图的不同状态,相反不同的视图应该使用不同的id。
在接下去的演讲中,Luca和Raj将讨论视图id对应用程序的数据和更新周期的实际影响。
现在让我们看看id怎么在你的代码中应用。
第一:explicit identity: 数据驱动的identifiers(标识符)
第二:structural identity:根据数据的类型和视图层级结构中的位置来区分视图
为了帮助理解上面两个概念,让我向你们介绍下我的朋友们(狗狗)。
注意这两张照片可以是不同的狗狗,就算看起来一样。
所以什么样的信息可以帮助我们标识我们的狗狗。一种方式是他们的名字。
这两只狗狗看起来一样,而且如果名字一样,我们就可以说他们是同一只狗狗。
但是如果他们有不同的名字,我们可以断定他们是不同的狗狗。
像这样分配名字或标识符是explicit identity的一种形式。
explicit identity 我们可以翻译成“显式标识符”。
显式标识符非常强大和灵活,但需要人为的在某个地方跟踪这些名字。
你可能已经使用过一种显式标识形式是指针标识,它已经在整个UIKit和AppKit中使用。
但是swiftUI并不使用指针标识,但是学习它将会帮助你更好的理解SwiftUI怎么和为什么不一样,让我们快速的看一看。
比如一个UIKit或AppKit视图层级结构,如下图。
因为UIViews和NSViews是类,他们都有一个唯一的指针指向内存区域,这个指针就是一个视图的显式标识符。我们可以使用各自的指针标识各自的视图,而且如果两个视图共享相同的指针,我们能断定他们是相同的视图。
但是SwiftUI并不使用指针,因为SwiftUI的视图是值类型,是结构体而不是类。
我们知道值类型并没有引用,所以SwiftUI不能用它来表示identity,而是用另一种表示方式就是:显式标识符。
比如,考虑下面一个搜救犬的列表。
List {
Section {
ForEach(rescueDogs, id:\.dogTagID) { rescueDog in
ProfileView(rescueDog)
}
}
Section("Status") {
ForEach(adoptedDogs, id:\.dogTagID) { rescueDog in
ProfileView(rescueDog, foundForeverHome: true)
}
}
}
这里的id参数是一种显式标识符。
每个救援犬的狗标签ID用于显式地标识其在列表中的对应视图。 如果搜救犬的集合发生了变化,SwiftUI可以使用这些id来了解到底发生了什么变化,并在列表中生成正确的动画。
在这种情况下,SwiftUI甚至能够正确地在不同Section之间执行移动动画。
让我们来看一个更高级的例子:
我们有一个 ScrollViewReader,通过点击底部的按钮来来跳来跳转到视图顶部,代码如下。
ScrollViewReader { proxy in
ScrollView {
HeaderView(rescueDog).id(headerID)
Text(rescueDog.backstory)
Button("Jump to Top") {
withAnimation {
proxy.scrollTo(headerID)
}
}
}
}
上面的id(_:) modifier 显式指定一个id, 我们的HeaderView在页面顶部。
然后我们可以通过proxy的scrollTo方法来滑动到指定视图。
这很好,不需要每个视图都指定id,需要在需要的视图指定id,但是没有指定id并不意味着没有id,因为每一个视图都有一个id。
这就是structural identity。SwiftUI使用根据的视图层级结构自动为你生成隐式id,我们无需手动指定。
让我介绍更多的朋友来帮助解释这里面的意思。
我们说我们有两只相识的狗狗,但是我们不知道他们的名字,所以我们需要他们的id。
如果我们能保证它们不动,我们就能根据它们坐的位置来识别它们,比如“狗在左边”或“狗在右边”。
我们用视图相对排列位置来区分彼此,这就是structural identity.
SwiftUI在它的API中利用了structural identity,一个典型的例子就是在当你在代码中使用if语句或其他条件语句时:
var body: some View {
if rescueDogs.isEmpty {
AdoptionDirectory(selection: $rescueDogs)
} else {
DogList(rescueDogs)
}
}
这个条件语句的结构给了我们一个标识每个视图的清晰的方式,第一个视图只在条件为true时显示,而第二个视图只在false时显示。
那意味着我们总能分辨出哪个视图是哪个,即使它们碰巧看起来很相似。
然而,这只有在SwiftUI能够静态地保证这些视图保持在它们所在的位置并且永不交换位置的情况下才有效。
SwiftUI通过查看视图层次结构的类型结构来实现这一点。
当SwiftUI查看视图时,它会看到它们的泛型类型——在本例中,我们的if语句被转换成一个_ConditionalContent视图,它的true和false内容是泛型的。
这个翻译是由ViewBuilder支持的,它是Swift中的一种结果构建器。
View协议隐式地将其body属性封装在ViewBuilder中,ViewBuilder从属性中的逻辑语句构建一个范型视图。
body属性的some View返回类型是一个表示此静态复合类型的占位符,将其隐藏起来,以便它不会干扰我们的代码。
使用这种泛型类型,SwiftUI可以保证true视图始终是AdoptionDirectory,而false视图始终是DogList,允许它们在背后各自被分配一个隐式的、稳定的id。
事实上,这是理解之前提到的“Good Dog,Bad Dog”app的关键。
在上面的代码中,我们有一个if语句,为每个条件分支定义不同的视图。
这将导致视图动画是fade in和fade out,因为SwiftUI知道if语句的每个分支代表一个具有不同标识的不同视图。
或者,我们可以只使用一个PawView来改变它的布局和颜色。
当它转换到一个不同的状态时,视图将平滑地滑到它的下一个位置。这是因为我们用唯一的id修改了同一个视图。
这两种策略都可以奏效,但SwiftUI通常推荐第二种方法。
默认情况下,尝试使用同一id,并提供流畅的转换动画。
这也有助于保存视图的生命周期和状态,这一点Luca将在后面更详细地讨论。
既然我们理解了structural identity,下面需要谈谈它的死敌:AnyView。
为了理解使用AnyView的影响,让我们看看它对视图接口的影响。
前面我们写了一个if语句用来切换AdoptionDirectory和DogList.
当SwfitUI看到这个代码,它会在右边看到泛型类型结构。
现在让我们看一个不同的例子,一个使用AnyView的例子。
这是我编写的一个帮助函数,用于获得一个代表狗的品种的视图。
函数中的每个条件分支都返回不同类型的视图,所以我把它们都包装在AnyView中,因为Swift需要整个函数返回单一类型。
但不幸的是:这也意味着SwiftUI无法看到我代码的条件结构。它只是将AnyView视为函数的返回类型。这是因为AnyView是所谓的“类型擦除包装器类型”——它从其范型签名中隐藏它包装的视图类型。
而且更严重的是,这段代码可读性非常差。
让我们看看是否可以简化这段代码,并让SwiftUI更多地看到它的结构。
首先,如果 sheepNearby == true,这个分支似乎是有条件地在我们的BorderCollieView旁边添加了一个SheepView。
我们可以通过在HStack中有条件地添加视图而不是在视图周围有条件地添加HStack来简化这一点。
这样,现在很容易看到每个分支都返回一个单一的视图,所以我们的局部变量dogView已经不需要了,我们在每个分支中使用return语句。
正如我们前面看到的,普通的SwiftUI View代码可以使用if语句返回不同类型的视图。 但是如果我们尝试从代码中删除返回语句和AnyViews,我们会看到一些错误和警告。
这是因为SwiftUI需要我们的助手函数提供一个单一的返回类型。
那么我们如何避免这些错误呢?回想一下,视图的body属性是特殊的,因为view协议隐式地将它封装在ViewBuilder中。
这将属性中的逻辑转换为一个单一的、通用的视图结构。所以我们可以手动在助手函数上面声明 @ViewBuilder, 这样就不会报错了。
现在代码经过优化,看起来非常好,避免使用AnyView,更加简洁易懂。
如果我们看一下结果的类型签名,它现在用一个条件内容树精确地复制了我们函数的条件逻辑,为SwiftUI提供了更丰富的视图视图和组件标识。
但还有一个地方可以改进。我们的功能的顶层只是针对犬种的不同情况进行匹配,我们可以将if语句改成switch语句。
现在就更容易快速理解所有不同的case了。
因为switch语句实际上只是条件语句的语法糖,结果视图右边的类型签名保持完全相同。
我们刚刚向您展示了anyview如何从代码中擦除了类型信息,并演示了如何通过利用ViewBuilder来消除不必要的anyview。
一般来说,我们建议尽可能避免使用AnyViews。
anyview会令代码难以阅读和理解。
而且因为AnyView对编译器隐藏静态类型信息,它有时会隐藏编译错误和警告。
最后,请记住,在不需要的时候使用AnyView会导致更糟糕的性能。如果可能的话,使用泛型来保留静态类型信息,而不是在代码中传递anyview。
好了,关于identity,介绍完毕。
通过显式标识,我们可以将视图的标识绑定到数据,或者提供自定义标识符来引用特定的视图。
通过结构标识,我们了解了SwiftUI如何根据视图层次结构中的类型和位置来标识视图。
现在我将把事情交给Luca来讨论identity是如何与视图的生命周期和状态联系起来的。
LifeTime
Thanks,Matt
现在我们已经理解了SwiftUI如果标识你的视图,让我们继续探索identity如何联系视图的生命周期和数据。
这将帮助你更好的理解SwiftUI如何工作。
当我们看到一只猫,它可能小睡一会,或者生气,但永远是那只猫,这时identity和生命周期联系起来了。
identity允许我们在一段时间为一个稳定的元素指定不同的值,换句话说,它允许我们在一段时间引入连续性。
你可能想知道这是如何应用于SwiftUI的?所以让我们回到Matt开发的cat-friendly app:
就像猫一样在不同的时刻有不同的状态,我们的视图在整个生命周期同样可以有不同的状态,每个状态都有不同的值。
Identity 在整个生命周期连接着这些不同的值。
让我们看一些代码来阐明这一点。
var body: some View {
PurrDecibelView(intensity: 25)
}
这里有一个简单的视图显示咕噜声的强度,SwfitUI会创建一个视图,它的强度值时25. 如果修改值为50,Swift UI需要再次调用此代码:
var body: some View {
PurrDecibelView(intensity: 50)
}
这是从同一个视图定义创建的两个不同的值。SwiftUI会保留一个值的副本,以便进行比较,并知道视图是否发生了变化,但是之后这个值被销毁了。
这里重要的是要理解视图值不同于视图标识。
view value != view identity
视图值是短暂的,您不应该依赖于它们的生命周期, 你能控制的是他们的identity。
当一个视图第一次创建并显示出来,SwiftUI给它分配一个identity,前面已经讨论过。
随着时间的推移,在更新的驱动下,视图的新值被创建。
但在SwiftUI看来,这些是相同的视图。一旦视图的identity发生改变或视图被删除,它的生命周期就结束了。
每当我们说到视图的生命周期,我们指的是与该视图相关的身份(identity)的持续时间。
能够将视图的identity与其生命周期联系起来是理解SwiftUI如何持久化状态的基础。
我们举State和StateObject作为例子
当SwiftUI正在看你的视图和看一个State或一个StateObject,它知道它需要在整个视图的生存期持久化这段数据。
换句话说,State和StateObject是与视图identity相关联的持久存储。
在视图identity的开始,当它第一次被创建时,SwiftUI将使用它们的初始值为State和StateObject分配内存空间。
我们关注下title的状态。
在视图的整个生命周期中,SwiftUI将在视图发生变化或视图体被重新评估时持久化此存储。
让我们看一个具体的例子,说明identity的变化如何影响状态的持久性。
这是一个有趣的例子,我们有相同的视图,但在两个不同的分支。
如果你还记得,因为structural identity,这两视图有不同的identity。
Matt已经讨论这如何影响动画,但是这还会影响持久化的状态。
让我们在实践中看看:
当第一次执行body并进入true分支时,SwiftUI将用初始值为状态分配内存。
在这个视图的整个生命周期中,SwiftUI将保持状态,因为它会因各种操作而发生改变,如果dayTime变成false,将进入另一个分支,SwiftUI知道这是另一个有着不同identity的视图,它会为这个false视图开辟一个新的内存空间,之前的ture view将会被释放,但是如果我们又回到true分支,那将是一个新的视图,所以SwiftUI创建新的存储空间,再从状态的初始值开始,老的视图会被释放。
所以结论就是,只要identity发生改变,状态就会被替换。
让我在这里暂停一下,确保您理解了这一点:您的状态的持久性与您的视图的生命周期相关联。
这是一个非常强大的概念因为我们可以清晰的区分什么是视图的本质----它的状态——并将其与它的identity联系起来。
其他的都可以从它推导出来。
您的数据是如此重要,以至于SwiftUI拥有一组数据驱动结构,这些结构将您的数据的identity用作视图的显式标识形式。
这方面的典型例子是ForEach。
让我们看看你能初始化一个ForEach的所有不同的方式。
这将帮助我们更好地理解这种类型
下面foreach代码,简单遍历range
ForEach(0..<5) { offset in
Text("\(offset)")
}
SwiftUI会把offset作为每个视图的identity,view的生命周期也是稳定的。
事实上,在动态range中使用这个初始化式是错误的:
ForEach(0..
它会有一个警告,让我们让事情更有趣,引入动态数据集合。
struct RescueCat {
var tagID: UUID
}
ForEach(rescueCats, id: \.tagID) { rescueCat in
ProfileView(rescueCat)
}
这个初始化器接受一个集合和一个指向作为标识符的属性的keypath。 这个属性必须是可哈希的,因为SwiftUI将使用它的值作为集合中元素生成的视图的一个标识(identity)。
稍后,Raj会给你展示一些例子,说明选择一个稳定的identity如何影响你的应用程序的性能和正确性。
为数据提供稳定标识的非常重要,而且标准库定义了Identifiable协议来支持这种功能。
SwiftUI充分利用了该协议。允许您省略keypath,并将数据元素类型遵循该协议:
struct RescueCat: Identifiable {
var tagID: UUID
var id: UUID { tagID }
}
ForEach(rescueCats) { rescueCat in
ProfileView(rescueCat)
}
我真正喜欢Swift的一点是,我们可以利用它的类型系统来精确约束我们的参数类型。请跟我一起看一看我们这里使用的初始化式的定义。
在这个简短的定义中有很多有趣的东西,所以让我们试着把它们拆开。
ForEach 需要两个主要参数, 一个集合,这里的范型类型是Data,以及从集合的每个元素生成视图的方法。这里约束了Data的Element 必须遵循Identifiable,并且ID 的类型和 Data.Element.ID相同。
这里给你一个直观的印象就是,ForEach在数据集合和视图集合之间定义了一个关系表。
但是事实上,最有趣的是我们约束了元素的类型必须是Identifiable. Identifiable协议的目的是允许你的类型提供一个稳定的identity,以便SwiftUI可以在它的生命周期跟踪你的数据。事实上,这与我们之前讨论的identity和生命周期的概念非常相似。
接受Identifiable类型和视图构建器的SwiftUI视图是数据驱动组件。
这些视图使用您提供的数据的identity来限定与之关联的视图的生命周期。
选择一个好的标识符来控制视图和数据生命周期 。
让我们来总结下:
视图的值是短暂的,您不应该依赖于它们的生命周期。
但他们的identity并非如此,而正是这种identity赋予了他们随时间的延续性。
您可以控制视图的identity,并且可以使用identity清楚地限定状态的生存期。
最后,SwiftUI充分利用了数据驱动组件的Identifiable协议,因此为数据选择一个稳定的标识符非常重要。
现在按照惯例,我要把话筒交给Raj。
Dependencies
Raj Ramamurthy:谢谢你,Luca!到目前为止,我们已经解释了什么是Identity以及它如何与视图的生命周期相关联。
接下来,我将深入研究SwiftUI如何更新UI。
我们的目标是为您提供一个更好的思维模型来构建SwiftUI代码。
我还会在最后举几个例子来概括所有内容。
为了抛出对依赖关系的讨论,让我们来看一个视图。
这是一个简单的视图,它显示一个奖励狗狗的按钮,
首先,让我们看看顶部,那有两个属性:一个是dog,一个是treat,视图依赖于这两属性。依赖项只是视图的一个输入,当一个依赖项改变时,
视图需要生成一个新的body。
body是为视图构建层次结构的地方。
深入到这个视图的层次结构中,我们有一个带有操作的按钮。
动作会触发视图依赖项发生变化。让我们把代码换成一个等价的图
当我们点击按钮,它发出一个动作:奖励狗狗,我们的狗狗一眨眼就把食物吞了下去
这就导致了狗的变化——也许他想要另一只。
因为依赖关系改变了,DogView产生了一个新的body。
让我们简化下这个图。
关注视图层次结构,注意我们的视图是如何形成树状结构的。
如果我们加上狗,把依赖关系放在最上面,它看起来还是像棵树。
然而,DogView并不是唯一一个有依赖的视图。
在SwiftUI中,每个视图都可以有自己的一组依赖项。
到目前为止,它看起来还是一棵树。
例如,其中一个后代可能也依赖于狗。这也可能发生在我们的其他依赖关系中。
最后实际上是一张图,我们称之为“依赖图”(dependency graph)
这个结构很重要,因为它允许SwiftUI只有效地更新那些需要新body的视图
比如,位于底部的依赖项。
如果我们检查这个依赖关系,它有两个依赖视图,如这个依赖项发生变化,只有这两个视图会失效, SwiftUI将调用每个视图的body,为每个视图生成一个新的body值。 SwiftUI将实例化每个失效视图的主体的值。这可能会导致更多依赖项的更改,但并不总是如此!因为视图是值类型,SwiftUI可以有效地比较它们,只更新视图的右边子集。 这是Luca前面讨论的另一种方式。
视图的值是短暂的。结构值只是用于比较,但是视图本身有更长的生命周期。这就是我们如何避免为中心的视图生成新的物体。
identity是依赖关系图的骨架。
正如Matt所说,每个视图都有identity,无论是显式指定的还是结构指定的。
SwiftUI通过这个identity将更改路由到正确的视图,并有效地更新UI
依赖关系有很多种。
我们在前面看到了一些关于treat属性和dog绑定的例子,但是你也可以通过使用Environment、State或任何可观察对象属性包装器来形成依赖关系。
接下来,我想谈谈如何在您的视图中改进identity的使用。
这将有助于SwiftUI更好地理解你的代码。
正如卢卡所说,一个视图的生命周期就是其identity的持续时间,这意味着identity的稳定性至关重要。 不稳定的identity会导致较短的视图生存期。
拥有一个稳定的identity也有助于提高性能,因为SwiftUI不需要持续创建新的视图,也不需要通过更新图(graph)来回折腾。
正如您前面看到的,SwiftUI使用生命周期来管理持久存储,因此稳定identity对于避免状态丢失也很重要。
让我们看一个代码示例来解释identity稳定性的重要性。
在这个例子中,我列出了我最喜欢的宠物。
我们在pet结构体上有一个identity。
但实际上有一个bug;每次我得到一只新宠物,屏幕上的一切都在闪烁!让我们暂停一下,看看这段代码。
你能找出bug在哪里吗?错误就在这里,在我们的Identifiable 协议实现中。
问题在于这里的identity不是稳定的,只要数据一变,我们会得到一个新的identity。
那怎么改正呢,使用pets数组的下标作为identity?这也同样有问题。
如果用下标, 视图现在通过各自宠物在集合中的位置来标识。
如果我决定我有一个新的第一个最喜欢的宠物,所有其他宠物将改变他们的identity,这可能会导致一个糟糕的bug。
在这个例子中,按钮在索引0处插入了一个新元素,但是因为最后一个索引是新元素,所以我们在末尾而不是开始处插入了一个元素。
这是因为,与计算形随机identity一样,索引不是稳定形式的identity。
在本例中,我们需要使用稳定identity,比如来自数据库的identity,或者来自宠物的稳定属性的identity。任何持久性标识符都是很好的选择。
现在我们的动画看起来很棒!但是稳定性并不是一个好的identity的唯一要求,另一个要求就是唯一性。
每个identity应该映射到单个视图。这确保了动画看起来很棒,性能流畅,层次结构的依赖性以最有效的形式反映出来。
让我们看另一个例子:
在这个例子中,我有一个我的宠物最喜欢的食物的视图。 每一种食物都有一个名字、一个表情符号和一个有效期。
我选择用名字来区分每一种食物。
在这一点上——我相信你能猜到——我们这里也有一个bug:如果存在相同名字的食物怎么办。
所以,我们应该使用序列号或其他唯一的ID。
这确保了所有正确的数据都显示在我们的视图中,它还将确保更好的动画和更好的性能。
当SwiftUI需要一个identity时,它就需要您的特别注意!使用随机identity时请小心,特别是在计算属性中。
通常,您希望所有identity都是稳定的。
identity不应该随时间而改变;新identity表示具有新生命期的新项。
另外,identity需要是唯一的!多个视图不能共享一个identity。
SwiftUI依赖于稳定性和唯一性让你的应用程序运行顺畅、无bug。
既然我们已经讨论了显性id(explicit identity),我想继续讲结构id(structural identity)。
还是拿之前的食物罐子做例子:
作为一个负责任的宠物爱好者,我只给我的宠物喂最好的、未过期的食物。
为了帮助我判断什么时候食物坏了,我添加了一个新的modifier,可以在食物过期时选择性地调暗食物单元格。
让我们看看这个modifier,可以看到我根据比较当前时间来决定什么时候调暗这个视图。
这个代码看着挺好,但有一个微妙的问题。
如果条件发生变化,我们的食物过期了,我们得到一个新的identity因为这里是一个分支。
正如Matt所讨论的,分支是structural identity的一种形式。
这意味着我们有两个内容副本,而不是一个可选的修改副本。
注意,这里的分支是在修饰符中。
为了清晰起见,我把修饰符和它的使用放在同一张幻灯片上,但在您的项目中,您可能会在不知情的情况下跨文件拥有这样的分支!当然,我们在这里讨论的所有内容都适用于视图和视图modifiers。
那么我们如何避免这种情况呢?一种方法是将分支折叠在一起,并将条件移动到不透明度修改器中,就像这样:
通过删除这个分支,我们已经正确地将这个视图描述为具有单一identity。
此外,将条件移动到不透明度修改器中有助于提高性能,因为我们已经严格限定了依赖代码的范围。
现在,当条件改变时,只有不透明度需要改变。
这里的技巧是,当条件为真时,不透明度为1,就像这样。
分支很好,他们的存在是有原因的,但没有必要地使用时,他们会导致很差的性能,令人惊讶的动画,以及状态丢失。
当您引入一个分支时,请暂停一下,并考虑您是在表示多个视图还是同一个视图的两个状态,如果是一个视图的两个状态,尽量避免用分支。
总结一下:我们向您展示了identity是神奇性能的一个秘密之一,我们讨论了显示和结构性identity,以及如何利用他们改善您的app。
从identity中,我们可以获得视图的生命周期,它控制视图关联的存储、转换等。
我们还解释了SwiftUI使用identity和生命周期来形成依赖关系,这些依赖关系由一个图表示,可以有效地更新UI。
在揭秘SwiftUI的同时,我们也为你提供了一些避免bug和提高应用性能的技巧。
既然您已经学会了这些技巧,那么就review下您的代码,看看它们是否能帮助您。
谢谢你们,继续构建牛逼的app吧。