onAppear( task )是 SwiftUI 开发者经常运用的一个修饰符,但一向没有威望的文档清晰它的闭包被调用的机遇。本文将经过 SwiftUI 4 供给的新 API ,证明 onAppear 的调用机遇是在布局之后、烘托之前。

原文发表在我的博客wwww.fatbobman.com

欢迎订阅我的大众号:【肘子的Swift记事本】

问题

同之前多篇博客相似,咱们仍是从 聊天室 的一个 问题 开始。

SwiftUI onAppear 的调用时机

请忽略比如中的写法是否合理和值得推荐,仅考虑为什么在榜首段代码中,呈现了数组越界的状况;以及第二段代码能够正确运转。

创立实例、求值、布局、烘托

在 SwiftUI 中,一个视图在它的生命周期中通常会经历四个阶段:

创立实例

视图树中,处于可显现分支的视图基本上都会经历的一个阶段。在一个视图的生计期中,SwiftUI 可能会多次创立视图实例。

因为惰性视图的优化机制,对于尚未处于可见区域的子视图,SwiftUI 不会创立其实例

求值

一个被显现的视图至少会经历一次的进程。因为 SwiftUI 的视图实际上是一个函数,SwiftUI 需求对视图进行求值( 调用 body 特点 )并保留核算成果。当视图的依靠( Source of truth )发生变化后,SwiftUI 会从头核算视图成果值,并与旧值进行比较。如发生变化,则用新值替换旧值。

布局

在核算好当时需求显现的视图所有的视图值后,SwiftUI 将进入到布局阶段。经过父视图向子视图供给建议尺度,子视图回来需求尺度这一进程,最终核算出完好的布局成果。

有关布局的流程请阅览 SwiftUI 布局 —— 尺度

烘托

SwiftUI 经过调用愈加底层的 API,将视图在屏幕上呈现的进程。此进程严厉意义上已经不属于 SwiftUI 的办理范畴了。

Appear 是相对于谁的?

在不少的词典中,appear 都被解释为例如 to come into sight; become visible 这样的意思。这会让开发者误以为 onAppear 是在视图烘托后( 运用者看到后 )才被调用的。但在 SwiftUI 中,onAppear 实际上是在烘托前被调用的。

假定排除了苹果起名呈现了过错这个原因,此时的 appear 更像是针对 SwiftUI 系统来说的。视图在完成了创立实例、求值、布局后( 完成了属于 SwiftUI 架构的办理流程 ),就算是 appear 于 SwiftUI 的“眼前”。

求证

口说无凭,本节咱们将用依据来证明上述揣度。

在写 SwiftUI 视图的生命周期研讨 一文时,咱们只能经过现象来揣度 onAppear 的调用机遇,随着版本的不断提高,SwiftUI 4 中为咱们供给了足够的东西让咱们能够取得愈加确实的依据。

判别视图正在求值

在视图中添加相似如下的代码,是咱们判别 SwiftUI 是否正在对视图进行求值的常用手段:

VStack {
  let _ = print("evaluate")
}

判别视图正处于布局阶段

在 4.0 中版本中,SwiftUI 供给了 Layout 协议,答应咱们创立自定义布局容器,经过创立符合该协议的实例,咱们便能够判别当时视图是否正处于布局阶段。

struct MyLayout: Layout {
    let name: String
    func sizeThatFits(proposal _: ProposedViewSize, subviews _: Subviews, cache _: inout ()) -> CGSize {
        print("\(name) layout")
        return .init(width: 100, height: 100)
    }
    func placeSubviews(in _: CGRect, proposal _: ProposedViewSize, subviews _: Subviews, cache _: inout ()) {}
}

上面的代码创立了一个固定回来 100 * 100 需求尺度的布局容器,在父视图询问其需求尺度时将经过控制台报告给咱们。

判别视图正准备烘托

虽然 SwiftUI 视图并没有供给能够展示该进程的 API,不过咱们能够运用 UIViewControllerRepresentable 协议来包装一个 UIViewController ,并经过它的生命周期回调办法来确定当时的状态。

struct ViewHelper: UIViewControllerRepresentable {
    func makeUIViewController(context _: Context) -> HelperController {
        return HelperController()
    }
    func updateUIViewController(_: HelperController, context _: Context) {
    }
    // SwiftUI 4 新增办法
    func sizeThatFits(_: ProposedViewSize, uiViewController _: HelperController, context _: Context) -> CGSize? {
        print("helper layout")
        return .init(width: 50, height: 50)
    }
}
final class HelperController: UIViewController {
    override func viewWillAppear(_: Bool) {
        print("will appear(render)")
    }
}

在上面的代码中,sizeThatFits 与 Layout 协议的 sizeThatFits 调用机遇共同,都是在布局进程中,父视图向子视图询问需求尺度时拜访。viewWillAppear 则是在 UIViewController 被呈现前( 能够理解为烘托前 ),会由 UIKit 调用。

经过 UIViewControllerRepresentable 封装的“视图”并非真实的视图,对于 SwiftUI 来说,它就是一块给出了需求尺度的黑洞,因而并不存在求值一说。

整合

有了上面的东西,经过下面的代码,咱们便能够完好地了解一个 SwiftUI 视图的处理进程,以及 onAppear 的调用机遇。

struct LayoutTest: View {
    var body: some View {
        MyLayout(name: "outer") {
            let _ = print("outer evaluate")
            MyLayout(name: "inner") {
                let _ = print("inner evaluate")
                ViewHelper()
                    .onAppear {
                        print("helper onAppear")
                    }
            }
            .onAppear {
                print("inner onAppear")
            }
        }
        .onAppear {
            print("outer onAppear")
        }
    }
}

输出如下:

outer evaluate
inner evaluate
outer layout
inner layout
helper layout
outer onAppear
helper onAppear
inner onAppear
will appear(render)

分析

经过上面的输出,能够清楚地了解视图处理的全进程:

  • SwiftUI 首先对视图进行求值( 由外向内 )
  • 在悉数求值完毕后开始进行布局( 由父视图到子视图 )
  • 在布局完毕后,调用视图对应的 onAppear 闭包( 次序不明,不要假定 onAppear 之间的执行次序 )
  • 烘托视图

由此能够证明,onAppear 确实是在布局之后,烘托之前被调用的。

解答

回到本文开始的问题。

榜首段代码

  • 对 VStack 进行求值
  • 核算到 Text ,创立 Text 实例
  • 创立实例时,需求调用 getWord 来获取参数
  • 此时因为 newWords 数组为空,因而呈现数组越界的过错

也就是说,在榜首段代码报错时,该视图甚至还没有进入到布局阶段,就更不必提调用 onAppear 了。

在不考虑运用绝对索引值是否正确的状况下,经过下面的代码,便能够避免问题的呈现:

if !newWords.isEmpty {
    Text(getWord(at:0))
}

第二段代码

  • 对 List 进行求值
  • 因为 ForEach 会根据 newWords 的数量进行子视图的处理,因而虽然此时 newWords 为空,但也不会有问题
  • 完成布局
  • 调用 onAppear 闭包,给 newWords 赋值
  • 因为 newWords 是该视图的 Source of truth ,发生改变后,导致视图从头改写
  • 重复上面的进程,此时 newWords 已经有值了,ForEach 将正常处理所有的子视图

总结

在本文中,咱们经过 SwiftUI 4 供给的新东西清晰了 onAppear 的调用机遇,或许这是新 API 开发时未曾想到的功能使用。

希望本文能够对你有所帮助。一起也欢迎你经过 Twitter、 Discord 频道 或博客的留言板与我进行沟通。

订阅下方的 邮件列表,能够及时取得每周的 Tips 汇总。

原文发表在我的博客wwww.fatbobman.com

欢迎订阅我的大众号:【肘子的Swift记事本】