ViewBuilder 研究(上)—— 掌握 Result builders

2022-07-28 13:04:15 浏览数 (1)

作为一个严重依赖 SwiftUI 的开发者,同视图打交道是最平常不过的事情了。从第一次接触 SwiftUI 的声明式编程方式开始,我便喜欢上了这种写代码的感觉。但接触地越多,碰到的问题也越多。起初,我单纯地将很多问题称之为灵异现象,认为大概率是由于 SwiftUI 的不成熟导致的。随着不断地学习和探索,发现其中有相当部分的问题还是因为自己的认知不够所导致的,完全可以改善或避免。

我将通过上下两篇博文,对构建 SwiftUI 视图的 ViewBuilder 进行探讨。上篇将介绍 ViewBuilder 背后的实现者 —— result builders ; 下篇将通过对 ViewBuilder 的仿制,进一步地探寻 SwiftUI 视图的秘密。

访问我的博客 www.fatbobman.com[1] 可以获得更好的阅读体验

本文希望达成的目标

希望在阅读完两篇文章后能消除或减轻你对下列疑问的困惑:

  • 如何让自定义视图、方法支持 ViewBuilder
  • 为什么复杂的 SwiftUI 视图容易在 Xcode 上卡死或出现编译超时
  • 为什么会出现 “Extra arguments” 的错误提示(仅能在同一层次放置有限数量的视图)
  • 为什么要谨慎使用 AnyView
  • 如何避免使用 AnyView
  • 为什么无论显示与否,视图都会包含所有选择分支的类型信息
  • 为什么绝大多数的官方视图类型的 body 都是 Never
  • ViewModifier 同特定视图类型的 modifier 之间的区别
  • SwiftUI 的隐式标识和显式标识之间的区别

什么是 Result builders

介绍

result builders 允许某些函数通过一系列组件中隐式构建结果值,按照开发者设定的构建规则对组件进行排列。通过对函数语句应用构建器进行转译,result builders 提供了在 Swift 中创建新的领域特定语言( DSL )的能力(为了保留原始代码的动态语义,Swift 有意地限制了这些构建器的能力)。

与常见的使用点语法实现的类 DSL 相比,使用 result builders 创建的 DSL 使用更简单、无效内容更少、代码更容易理解(在表述具有选择、循环等逻辑内容时尤为明显),例如:

使用点语法( Plot[2] ):

代码语言:javascript复制
.div(
    .div(
        .forEach(archiveItems.keys.sorted(by: >)) { absoluteMonth in
            .group(
                .ul(
                    .forEach(archiveItems[absoluteMonth]) { item in
                        .li(
                            .a(
                                .href(item.path),
                                .text(item.title)
                            )
                        )
                    }
                ),
                .if( show, 
                    .text("hello"), 
                  else: .text("wrold")
                 ),
            )
        }
    )
)

通过 result builders 创建的构建器 ( swift-html[3] ):

代码语言:javascript复制
Div {
    Div {
        for i in 0..<100 {
            Ul {
                for item in archiveItems[i] {
                    li {
                        A(item.title)
                            .href(item.path)
                    }
                }
            }
            if show {
                Text("hello")
            } else {
                Text("world")
            }
        }
    }
}

历史与发展

自 Swift 5.1 开始,result builders 便随着 SwiftUI 的推出隐藏在 Swift 语言之中(当时名为 function builder)。随着 Swift 与 SwiftUI 的不断进化,最终被正式纳入到 Swift 5.4 版本之中。目前苹果在 SwiftUI 框架中大量地使用了该功能,除了最常见的视图构建器(ViewBuilder)外,其他还包括:AccessibilityRotorContentBuilder、CommandsBuilder、LibraryContentBuilder、SceneBuilder、TableColumnBuilder、TableRowBuilder、ToolbarContentBuilder、WidgetBundleBuilder 等。另外,在最新的 Swift 提案中,已出现了 Regex builder DSL[4] 的身影。其他的开发者利用该功能也创建了不少的 第三方库[5]

基本用法

创建构建器类型

一个结果构建器类型必须满足两个基本要求。

  • 它必须通过@resultBuilder进行标注,这表明它打算作为一个结果构建器类型使用,并允许它作为一个自定义属性使用。
  • 它必须至少实现一个名为buildBlock的类型方法

例如:

代码语言:javascript复制
@resultBuilder
struct StringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.map{"⭐️"   $0   "


	

0 人点赞