mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
feat: 精准测试(OC、Swift)
This commit is contained in:
@@ -8,7 +8,7 @@ Xcode 新建项目 Language 选择 Swift 语言、Interface 选择 SwiftUI。然
|
||||
|
||||
可以看到下面的文件:
|
||||
|
||||
<img src="./../assets/SwiftUIDemo1.png" style="zoom:45%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIDemo1.png" style="zoom:45%">
|
||||
|
||||
奇怪的事情发生了:AppDelegate 不见了,也没地方构建 keyWindow,怎么办?为什么文件叫 `SwiftUIDemoApp`?
|
||||
|
||||
@@ -79,13 +79,13 @@ struct SwiftUIDemoApp: App {
|
||||
|
||||
SwiftUI 的文档写的还是不错。
|
||||
|
||||
<img src="./../assets/SwiftUIOffcialDocument.png" style="zoom:25%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIOffcialDocument.png" style="zoom:25%">
|
||||
|
||||
|
||||
|
||||
ContentView.swift 及其效果如下:
|
||||
|
||||
<img src="./../assets/SwiftUISimpleDemo.png" style="zoom:25%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUISimpleDemo.png" style="zoom:25%">
|
||||
|
||||
上面的代码 `some view` 中,view 是 SwiftUI 一个核心的协议,代表了闭包中元素描述。如下代码所示,其是通过一个 associatedtype 修饰的,带有这种修饰的协议不能作为类型来使用,只能作为类型约束来使用。
|
||||
|
||||
@@ -99,15 +99,15 @@ ContentView.swift 及其效果如下:
|
||||
|
||||
- Xcode 支持预览
|
||||
|
||||
<img src="./../assets/SwiftUIDemo2.png" style="zoom:45%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIDemo2.png" style="zoom:45%">
|
||||
|
||||
- 在预览界面选中某个空间,同时按住 `command + 单击`,可以调出一个操作面板。第一个是 UI 检查器,可以查看和修改
|
||||
|
||||
<img src="./../assets/SwiftUIDemo3.png" style="zoom:45%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIDemo3.png" style="zoom:45%">
|
||||
|
||||
在代码区域选中控件,同时按住 `command + 单击`,同样可以调出一个操作面板
|
||||
|
||||
<img src="./../assets/SwiftUIDemo4.png" style="zoom:45%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIDemo4.png" style="zoom:45%">
|
||||
|
||||
- 预览模式下,支持代码和预览界面的实时刷新同步。
|
||||
|
||||
@@ -269,7 +269,7 @@ extension View {
|
||||
|
||||
View 上大多数调用的方法都称为 `Modifier`,一种是为 `原地Modifier` ,另外一种为 `封装类Modifier`。`原地Modifier` 是返回同样类型的 View,`封装类Modifier` 则可以返回不同类型的 View,在开发中我们经常需要自定义 `ViewModifier` 来对 View 进行特定的变换操作。
|
||||
|
||||
<img src="./../assets/SwiftUIViewModifier.png" style="zoom:40%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewModifier.png" style="zoom:40%">
|
||||
|
||||
|
||||
|
||||
@@ -768,7 +768,7 @@ struct SwiftUIDemoApp: App {
|
||||
}
|
||||
```
|
||||
|
||||
<img src="./../assets/UIKitCompoentWrappedForSwiftUI.png" style="zoom:35%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/UIKitCompoentWrappedForSwiftUI.png" style="zoom:35%">
|
||||
|
||||
|
||||
|
||||
@@ -891,7 +891,7 @@ struct SwiftUIDemoApp: App {
|
||||
|
||||
SwiftUI 是个 UI 框架、也是个组件库,核心是为了解决 UI 构建复杂、繁琐的问题。Redux 在前端由来已久,有 store、state、action、middleware、reducer 等角色,多个角色各司其职,不存在团队规范和约定后不遵守的情况。通过 store 来管理状态,状态变化后,使用到该状态的 UI 组件会收到通知,更新 UI。用户点击操作 UI,产生 action,action 经历过一系列 middleware 后来到了 store,store 让 reducer 根据 action 和当前的 state 计算,得到一个新的 state。新的 state 变化了,使用到的地方的 UI 也会自动更新。(数据和 UI 的双向绑定)
|
||||
|
||||
<img src="./../assets/2019-06-26-Redux-Structures.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/2019-06-26-Redux-Structures.png" style="zoom:30%">
|
||||
|
||||
对比 MVC:
|
||||
|
||||
@@ -917,7 +917,7 @@ Apple 推出了 SwiftUI,但没有像最早 MVC 一样,在 SwiftUI 中推出
|
||||
|
||||
安装依赖:File -> Add Packages,输入 `swift-composable-architecture` 搜索,点击右下角 Add Package 即可。
|
||||
|
||||
<img src="./../assets/SwiftTCADemoStep1.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftTCADemoStep1.png" style="zoom:30%">
|
||||
|
||||
然后开始开发:先编写 Reducer 部分,再开发相关 UI
|
||||
|
||||
@@ -1031,7 +1031,7 @@ struct ContentView_Previews: PreviewProvider {
|
||||
}
|
||||
```
|
||||
|
||||
<img src="./../assets/SwiftTCADemo1.png" style="zoom:35%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftTCADemo1.png" style="zoom:35%">
|
||||
|
||||
说明:
|
||||
|
||||
@@ -1092,11 +1092,11 @@ final class TCADemoTests: XCTestCase {
|
||||
|
||||
可以看到如果某个单测 case 失败,则会清楚的显示错误的信息。
|
||||
|
||||
<img src="./../assets/SwiftTCAUnitTestDemo1.png" style="zoom:25%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftTCAUnitTestDemo1.png" style="zoom:25%">
|
||||
|
||||
如果需要在测试的时候使用“重复测试”功能,右击测试按钮,在弹出框里做重复测试的配置修改。
|
||||
|
||||
<img src="./../assets/SwiftTCAUnitTestDemo2.png" style="zoom:25%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftTCAUnitTestDemo2.png" style="zoom:25%">
|
||||
|
||||
### 动手做一个简易版 Redux
|
||||
|
||||
@@ -1215,7 +1215,7 @@ struct ReduxDemoView_Previews: PreviewProvider {
|
||||
|
||||
实现效果如下:
|
||||
|
||||
<img src="./../assets/SwiftUIReduxDemo.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIReduxDemo.png" style="zoom:30%">
|
||||
|
||||
## 核心技术
|
||||
|
||||
@@ -1299,7 +1299,7 @@ while let input = readLine() {
|
||||
|
||||
下面是 `CFRunLoop` 的示意图,将其与我们的命令行应用程序进行比较。
|
||||
|
||||
<img src="./../assets/RunLoopAndSimpleVersion.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/RunLoopAndSimpleVersion.png" style="zoom:30%">
|
||||
|
||||
如果你在 Xcode 调试器中暂停一个 iOS 应用程序,主线程的堆栈信息中便会出现下面的调用栈
|
||||
|
||||
@@ -1389,7 +1389,7 @@ Demo
|
||||
|
||||
|
||||
|
||||
<img src="./../assets/RunLoopAndSwiftUI.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/RunLoopAndSwiftUI.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -1470,7 +1470,7 @@ SwiftUI 中的渲染循环可能隐藏得很好,它所使用的技术与我们
|
||||
|
||||
#### 渲染流程
|
||||
|
||||
<img src="./../assets/SwiftUIRenderProcess" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIRenderProcess" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -1506,7 +1506,7 @@ SwiftUI 内部是如何处理开发者编写的描述性代码的呢?其内部
|
||||
|
||||
#### 视图标识(View Identity)
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo1.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo1.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -1514,7 +1514,7 @@ SwiftUI 内部是如何处理开发者编写的描述性代码的呢?其内部
|
||||
|
||||
所以当 SwiftUI 处理你的界面描述时,它也需要 Identity 这个关键信息区分视图是否是同一个。
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo2.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo2.png" style="zoom:30%">
|
||||
|
||||
让我们来看下上面这个 Good Dog, Bad Dog 的小应用,你可以点击屏幕上的任何位置来切换狗狗的状态。但是我们从技术层面分析,上面的界面可以有两种 SwiftUI 的描述方式:
|
||||
|
||||
@@ -1540,7 +1540,7 @@ Identity 既然这么重要,那么开发者是如何用代码来定义的呢
|
||||
|
||||
##### 声明式 Identity
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo3.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo3.png" style="zoom:30%">
|
||||
|
||||
就像上面这两只狗狗,仅通过图片,很难判断这是不是同一只狗狗,但如果我们能用名字来标识它们。就很容易得出结论。像这样给狗狗起名字来标识它们的方式,就是在显式声明 Identity。
|
||||
|
||||
@@ -1548,7 +1548,7 @@ Identity 既然这么重要,那么开发者是如何用代码来定义的呢
|
||||
|
||||
我们可以通过视图的指针来标识每个视图,如果多个视图指针,都共享同一块内存空间,那么它们其实是同一个视图,如下图所示:
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo4.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo4.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -1556,13 +1556,13 @@ Identity 既然这么重要,那么开发者是如何用代码来定义的呢
|
||||
|
||||
其实,SwiftUI 是用另外一种形式来显式标识 View。通过下面的例子能更好的理解,例如在这个救援犬列表里,用 dogTagID KeyPath 获取相应属性,在参数里指定到 id 上,就是在显式声明 View 的 Identity。这样就能标识出每条数据对应的展示视图。一旦列表数据发生变化, SwiftUI 可以根据这些 ID 来判断,哪些视图需要新生成,哪些视图重复使用,只需要执行动画。
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo5.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo5.png" style="zoom:30%">
|
||||
|
||||
##### 结构性 Identity
|
||||
|
||||
不显式声明 Identity,这并不意味着这些 View 根本没有 Identity,也就是说每个 View 都有一个 Identity,即使它不是显式声明出来的。在这种情况下 SwiftUI 内部会对没有显式 Identity 的 View 根据它的描述层级结构生成一种隐式的 Identity,就叫做结构性 Identity。
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo6.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo6.png" style="zoom:30%">
|
||||
|
||||
如上图,假设我们有两只相似的狗狗,但我们不知道它们的名字,我们仍然还要标识出它们。这时候可以通过它们坐的位置来标识,如 `左边的狗` 或 `右边的狗`。
|
||||
|
||||
@@ -1570,13 +1570,13 @@ Identity 既然这么重要,那么开发者是如何用代码来定义的呢
|
||||
|
||||
SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来标识 View。一个典型的例子就是在 SwiftUI 中 使用 if else 条件判断的时候,条件语句的结构使得 SwiftUI 能够明确的识别每个 View,如下图,第一个 AdoptionDirectory 只在条件为 True 的时候显示,第二个 DogList 只在条件为 False 的显示。
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo7.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo7.png" style="zoom:30%">
|
||||
|
||||
但是 SwiftUI body 计算属性需要一个明确一致的返回类型,但 if else 条件判断使得返回类型不一致了,会引起编译失败。这时候 SwiftUI 引入了一个的黑魔法 - **ViewBuilder (默认是附加在 body 计算属性上的,不需要开发者单独指定)**。ViewBuilder 帮助 SwiftUI 把各种条件判断,封装成 _ConditionalContent 的数据结构。但为了区分在不同分支下的类型不同,用泛型来进行了区分,这样即保证了返回数据的一致性,又保证了 SwiftUI 内部可以通过泛型识别出不同分支下结构性 Identity。
|
||||
|
||||
见源代码:
|
||||
|
||||
<img src="./../assets/SwiftUIViewBuilder.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewBuilder.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -1584,7 +1584,7 @@ SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来
|
||||
|
||||
其实,如果你回到 UIKit 中理解,也是一样的,我们在 UIView 上执行动画的时候,一般也是在同一个 UIView 的实例里去动态改变它的属性去修改样式, 才会有那种平滑过渡的效果;相反,如果虽然是同一个类型的 UIView,但是对应的是不同的实例,去做那种平滑的过渡效果,也是很难实现的。
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo8.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo8.png" style="zoom:30%">
|
||||
|
||||
综上,**在使用结构性 Identity 的时候,第二种描述 View 的方式是更好的选择。应该尽量避免切换 Identity,这样做会给动画和性能都带来良好的效果,也有利于维持视图的生命周期和数据状态。**
|
||||
|
||||
@@ -1594,7 +1594,7 @@ SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来
|
||||
|
||||
说起 AnyView ,这家伙绝对是 Identity 的克星。
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo9.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo9.png" style="zoom:30%">
|
||||
|
||||
上图是一个使用 AnyView 的示例代码。在这个自定义 View 中,为了保证最终返回一个明确一致的数据类型,每个分支都用一个 AnyView 包裹起来。由于 AnyView 隐藏了所包装视图的类型,让 SwiftUI 无法在条件判断中识别出结构性 Identity,在 SwiftUI 眼里,它看到的都是一些擦除类型的 AnyView,更要命的是,这段代码阅读起来特别困难。
|
||||
|
||||
@@ -1607,7 +1607,7 @@ SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来
|
||||
|
||||
重构后,最终代码和 View 层级结构如下图:
|
||||
|
||||
<img src="./../assets/SwiftUIViewIdentityDemo10.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIViewIdentityDemo10.png" style="zoom:30%">
|
||||
|
||||
一般情况下,还是尽量避免使用 AnyView,因为 AnyView 有如下缺陷:
|
||||
|
||||
@@ -1623,13 +1623,13 @@ SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来
|
||||
|
||||
#### Lifetime 与 Identity 的关系
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityLifeCycle1.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityLifeCycle1.png" style="zoom:30%">
|
||||
|
||||
如上图,这里有个叫 Theseus 的小猫。他在一天中可能有各种不同的状态,一会装可爱,一会睡觉,一会发火,但是无论处于何种状态,他都是那只叫 Theseus 的小猫。
|
||||
|
||||
视图在整个生命周期内有各种不同的状态,每个状态在 SwiftUI 中由不同的 View 实例(值类型)来描述,而 Identity 将这些不同状态下的 View 值随着时间的推移关联起来,它们都对应着同一个视图元素。这就是 Identity 与视图生命周期建立联系的本质。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityLifeCycle2.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityLifeCycle2.png" style="zoom:30%">
|
||||
|
||||
让我们用上面的代码来更清晰的理解这一点,这里我们有一个简单的自定义 View - PurrDecibelView,用来显示猫叫声的强度。一开始的时候 SwiftUI 调用 body 计算属性,获取到叫声为 25 的 View,但是突然小猫饿了,希望获得更多的关注,叫声变大为50,这时候 SwiftUI 监听到叫声这个状态的变化,重新调用 body 计算属性,获取到一个全新的 View,这两个 View 是完全截然不同的两个值。SwiftUI 会在后台对这两个值进行对比并对比出哪些部分发生了变化。得出对比结果后,告诉渲染视图执行变化部分的渲染操作,同时,用完的 View 值也会被销毁。
|
||||
|
||||
@@ -1637,7 +1637,7 @@ SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来
|
||||
|
||||
所以,如下图,我们经常用到的生命周期方法 onAppear 和 onDisappear,其实是在视图显示和消失的时候触发,而不是 View 创建和销毁的时候触发。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityLifeCycle3.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityLifeCycle3.png" style="zoom:30%">
|
||||
|
||||
所以最终我们得出如下公式来阐述 View,LifeTime,Identity 三者之间的关系:
|
||||
|
||||
@@ -1656,11 +1656,11 @@ SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来
|
||||
|
||||
如下图的 CatRecorder 自定义 View,每次的 title 发生变化,由于他被 @State 修饰,SwiftUI 内部会在内存中保存这个数据,并且监听他的变化,一旦发生变化,就调用 body,重新计算。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityLifeCycle4.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityLifeCycle4.png" style="zoom:30%">
|
||||
|
||||
下面,让我们来看一下在有分支的情况下,视图生命周期和数据状态之间的关系。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityLifeCycle5.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityLifeCycle5.png" style="zoom:30%">
|
||||
|
||||
如上图代码,分支里的两个 CatRecorder 由于结构性 Identity 不同,所以它们被 SwiftUI 视为是两个不同的视图。之前说过这样会影响动画效果,其实也会影响它们内部数据状态的维持。
|
||||
|
||||
@@ -1676,7 +1676,7 @@ SwiftUI 几乎在所有地方都采用了这种结构化 Identity 的方式来
|
||||
|
||||
保证 Identity 稳定,这一点非常重要,尤其是在使用数据驱动型的列表控件时,在下面这些控件中,往往都需要用数据的 id 来给 View 显式声明 Identity。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityStableDemo1.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityStableDemo1.png" style="zoom:30%">
|
||||
|
||||
下面两张图是两种不同的 ForEach 的用法。其中第一种用法是一个常数的 Range,SwiftUI 可以直接用 Range 的值来为视图生成 Identity,以确保在视图的整个生命周期内 Identity 是稳定的。但当使用一个动态的 Range 时,会导致声明式的 Identity 数值是不可预期的,Identity 一旦切换,视图都会重新生成,这样就会出现性能问题。 所以在 Xcode 12 的时候,检查到这种使用方式会编译报错,而在新版本的 Xcode 13 这将变为一个警告 (beta 版本似乎不生效)。
|
||||
|
||||
@@ -1692,7 +1692,7 @@ ForEach(0..<sheeps) { offset in
|
||||
|
||||
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityStableDemo2.png" style="zoom:40%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityStableDemo2.png" style="zoom:40%">
|
||||
|
||||
|
||||
|
||||
@@ -1747,15 +1747,15 @@ struct DogView: View {
|
||||
|
||||
该 View 有两个属性 dog 和 treat,它们都可以理解为视图的依赖关系。依赖关系就是视图更新的入口。当依赖关系发生变化时,会重新调用 View 的 body,获取整个 View 的层级描述信息。在这个例子中,描述的就是一个有触发行为的按钮。他对应的视图层级结构如下:
|
||||
|
||||
<img src="./../assets/SwiftUIDependencies1.png" style="zoom:40%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIDependencies1.png" style="zoom:40%">
|
||||
|
||||
看上面这张图的话,是一个树结构,但是有可能多个视图都依赖同一个状态。有可能某个子视图也依赖顶级视图中的状态。情况越来越复杂后,这就不再是一个树结构。重新整理,避免让连接线之间交叉,如下图,可以看出它们之间的关系实际上是一个图结构。我们可以称之为**依赖关系图**。
|
||||
|
||||
<img src="./../assets/SwiftUIDependencies2.png" style="zoom:40%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIDependencies2.png" style="zoom:40%">
|
||||
|
||||
深入的理解这个依赖关系图很重要,因为它保证了 SwiftUI 只更新那些需要重新调用 body 的 View。以最底部的依赖关系为例。如果我们检查这个依赖关系,会发现有两个 View 依赖它,当依赖的数据状态发生变化,只有这两个 View 会被标记为无效。同时 SwiftUI 开始调用每个视图的 body 计算属性,只为标记为无效的视图产生一个新的 body 值。
|
||||
|
||||
<img src="./../assets/SwiftUIDependencies3.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIDependencies3.png" style="zoom:30%">
|
||||
|
||||
状态管理工具,在 SwiftUI 依赖关系的建立就是通过它们来实现的:
|
||||
|
||||
@@ -1783,11 +1783,11 @@ Identity 就是依赖关系图的灵魂,重要性不言而喻。正如之前
|
||||
|
||||
在下图的例子中,每次都生成一个 UUID 和 直接用 Indices 来显式声明 Identity 都是不稳定的方式,因为它们都会随着时间推移发生变化,不能准确地标识一个视图,最终导致的结果就是,当我们在列表头部新插入数据时,整个列表都会重新刷新。相反,我们如果用一个 databaseID 就是可以的,因为这个 ID 只对应一个数据,能够清晰的标识一个与该数据对应的视图。这时候我们在头部新插入数据,所有的动画效果都非常自然了。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityStable1.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityStable1.png" style="zoom:30%">
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityStable2.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityStable2.png" style="zoom:30%">
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityStable3.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityStable3.png" style="zoom:30%">
|
||||
|
||||
#### 唯一性
|
||||
|
||||
@@ -1801,9 +1801,9 @@ Identity 就是依赖关系图的灵魂,重要性不言而喻。正如之前
|
||||
|
||||
像下面的代码中使用 name 的 KeyPath 来给 View 显式声明 Identity,是不合理的,因为我们无法保证 name 的唯一性,一旦出现重名的情况,新的视图很有可能不会展示出来。但当把 name 换成 serialNumber,一切都正常了
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityUnique1.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityUnique1.png" style="zoom:30%">
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityUnique2.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityUnique2.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
@@ -1811,13 +1811,13 @@ Identity 就是依赖关系图的灵魂,重要性不言而喻。正如之前
|
||||
|
||||
上面,我们都是用声明式 Identity 来说明如何改进 Identity,接下来看看如何改进结构性 Identity。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityBranchLess1.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityBranchLess1.png" style="zoom:30%">
|
||||
|
||||
上面的代码,乍一看似乎没什么问题。但是仔细分析会发现,这里有个性能问题。**content 在不同的分支条件下,会产生不同的结构性 Identity,这就导致了分支切换后针对同一个 View 会生成两个不同的视图元素,也就是在内存中分配两份内存空间**。这点其实是可以避免的。虽然这里我们很轻易的发现了这个问题,但当项目大了之后,有可能这些 ViewModifier 的代码都不在一起,所以这种问题很容易被忽视。
|
||||
|
||||
修改:把分支结构去掉,改为在 opacity 修饰器上添加三目运算的方式来动态修改透明度。由于去掉了分支结构,所以 content 只会生成单一的结构性 Identity,也就避免了不必要的内存开销,提高了性能。
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityBranchLess2.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityBranchLess2.png" style="zoom:30%">
|
||||
|
||||
像上面代码直接把透明度设置为 1,也就是跟初始状态一致,其实 SwiftUI 发现这种情况是不执行任何渲染操作的。我们把这样的修饰器称为 "惰性修饰器",因为它们不影响渲染的结果。
|
||||
|
||||
@@ -1829,7 +1829,7 @@ Identity 就是依赖关系图的灵魂,重要性不言而喻。正如之前
|
||||
|
||||
在下图中还给出了一些其他的惰性修饰器作为参考:
|
||||
|
||||
<img src="./../assets/SwiftUIIdentityBranchLess3.png" style="zoom:30%">
|
||||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftUIIdentityBranchLess3.png" style="zoom:30%">
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user