SwiftUI 和 Swift 5.1 新特性(4) 蘋果先斬後奏?Function Builder 造就 SwiftUI 的 DSL

咱們首先經過下面這個例子回顧一下已經學習過的 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)
      }
    }
  }
}
複製代碼

咱們終於來到了 SwiftUI 中所包含的最後一個重要特性:FunctionBuilder,之因此放在最後一篇中來說,是由於它到目前爲止仍舊是一個還未通過 Swift Evolution 評審的語言特性,蘋果爲了趕 WWDC 19 的時間點,所以先斬後奏趕鴨子上架了,所以本文討論的細節可能在未來發生變化,可是因爲 iOS 13 發佈的迫近,應當不會推倒重來。swift

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爲例:ide

// 定義
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 轉換後的代碼:post

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>

複製代碼

若是根據不一樣條件返回不一樣的視圖,那麼生成的類型中包含兩個類型。ui

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>)>>spa

結語

從命名 @_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

掃描下方二維碼,關注「面試官小健」

相關文章
相關標籤/搜索