Swift

Swift 真的是开源语言吗?SwiftUI 快一周年了,@_f

2020-06-21  本文已影响0人  面试官小健

WWDC 20 快要到了,作为 iOS 开发者非常关注的一点是 Swift UI 在这一年中有着怎样的发展。众所周知,SwiftUI 基于许多对于 Swift 语言 DSL 级别的改进,最重要的一个就是 FunctionBuilder,记得 WWDC 19 中推出时,引发了大家的质疑,因为这个语言特性没有通过语言的 proposal 就被官方UI框架所使用,时至今日,它仍旧是个非正式语言特性。这阻止了开源社区利用 FunctionBuilder 建立出其他的出色框架,奉劝苹果不要在错误的道路上越走越远。

言归正传,在学习 FunctionBuilder 之前,我们首先通过下面这个例子回顾一下已经学习过的 SwiftUI 中的 Swift 5.1 新特性:some SwiftUI 和 Swift 5.1 新特性(1) 不透明返回类型 Opaque Result Type,以及@State@Binding背后的 @propertyDelegate SwiftUI 和 Swift 5.1 新特性(2) 属性代理Property Delegates,和 @dynamicMemberLookup SwiftUI 和 Swift 5.1 新特性(3) Key Path Member Lookup

struct SlideViewer: View {
  @State private var isEditing = false
  @Binding var slide: Slide

  var body: some View {
    VStack {
      Text("Slide #\(slide.number)")
      if isEditing {
        TextFiled($slide.title)
      }
    }
  }
}

1. SwiftUI DSL 的需要

我们仔细分析下上述 DSL 代码中的语法需要:

  1. 从表达方式上从简:尽量省略不必要的逗号,return,中括号等等。
  2. 支持简单的逻辑控制,比如 if 控制语句。
  3. 强类型:some View 代表了一个复合的强类型,在 View 发生改变的时候,复合的强类型有助于做 View diff 优化。
  4. 与 Swift 已有的语法不冲突。

2. 通过 ViewBuilder 来理解 @_functionBuilder

就像 @propertyDelegate 用来修饰 State一样,@_functionBuilder 用来修饰 ViewBuilder,这里同样 ViewBuilder 也不过是一个编译器会使用它、并且对它所包含的方法有一定要求的类型。那么 ViewBuilder 在哪里呢?其实就在各种容器类型的最后一个闭包参数中,以VStack为例:

// 定义
struct VStack<Content> where Content : View {
  init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
  @ViewBuilder content: () -> Content)
    
}

// 使用
struct ContentView : View {
  var body: some View {
    VStack(alignment: .leading) {
      Text("Hello, World")
      Text("Leon Lu")
    }
  }
}

上面这个例子中,我们看到 SwiftUI 中如何在一个容器类型 VStack 的构造函数的闭包中平铺其包含的两个 Text;另一方面,在闭包的函数声明中,我们看到了 @ViewBuidler 的修饰。其实不难推断,为了能编译过,ViewBuidler 对于这个闭包中的代码在编译阶段“动了手脚”,那么这是如何做到的呢?来看 ViewBuilder 中的关键方法:

static func buildBlock() -> EmptyView
static func buildBlock<Content>(Content) -> Content
static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)>
static func buildBlock<C0, C1, C2>(C0, C1, C2) -> TupleView<(C0, C1, C2)>
static func buildBlock<C0, C1, C2, C3>(C0, C1, C2, C3) -> TupleView<(C0, C1, C2, C3)>
static func buildBlock<C0, C1, C2, C3, C4>(C0, C1, C2, C3, C4) -> TupleView<(C0, C1, C2, C3, C4)>
...

我们的两个 Text 的例子中,编译器自动(根据名称的约定)使用了 static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)> 方法,这时候VStack的类型就成为了 VStack<TupleView<(Text,Text)>> 了。经过 ViewBuilder 转换后的代码:

struct ContentView : View {
  var body: some View {
    VStack(alignment: .leading) {
      ViewBuilder.buildBlock(Text("Hello, World"), Text("Leon Lu"))
    }
  }
}

值得一提的是,由于 buildBlock 的 overload 版本最多泛型参数是 10 个。所以当超过 10 个的时候可以使用 Group包一下; 如果有循环可以展开,则可以使用 ForEach

3. FunctionBuilder 分支条件的情况

ViewBuilder 中还有两个函数被用来构建含分支条件时候的类型

static func buildEither<TrueContent, FalseContent>(first: TrueContent) ->
 ConditionalContent<TrueContent, FalseContent>

static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> 
 ConditionalContent<TrueContent, FalseContent>

如果根据不同条件返回不同的视图,那么生成的类型中包含两个类型。

struct SlideViewer: View {
  @State private var isEditing = false
  @Binding var slide: Slide

  var body: some View {
    VStack {
      Text("Slide #\(slide.number)")
      if isEditing {
        TextFiled($slide.title)
      } else {
        Text(slide.title)
      }
    }
  }
}

此时,VStack的类型变成了 VStack<TupleView<(Text, ConditionalContent<TextField,Text>)>>

结语

从命名 @_functionBuilder 中包含的下划线就可以看出,Function Builder 还有一定微调的可能性,因此文中以实用主义 ViewBuilder 的视角来介绍 Function Builder 是什么。

SwiftUI 和 Swift 5.1 新特性 系列文章到此为止暂告段落,如有需要会继续更新和补充。

感谢大家厚爱,今后会有更多的文章带给大家,希望大家喜欢。

相关文章:

SwiftUI 和 Swift 5.1 新特性(1) 不透明返回类型 Opaque Result Type

SwiftUI 和 Swift 5.1 新特性(2) 属性代理Property Delegates

SwiftUI 和 Swift 5.1 新特性(3) Key Path Member Lookup

扫描下方二维码,关注“面试官小健”

image
上一篇 下一篇

猜你喜欢

热点阅读