62
задан 5 June 2019 в 14:54

2 ответа

, Поскольку Martin говорит , если Вы смотрите на документацию для [1 135] VStack init(alignment:spacing:content:) , Вы видите, что content: параметр имеет атрибут @ViewBuilder:

init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
     @ViewBuilder content: () -> Content)

Этот атрибут относится к ViewBuilder тип, который, если Вы смотрите на сгенерированный интерфейс, похож:

@_functionBuilder public struct ViewBuilder {

    /// Builds an empty view from an block containing no statements, `{ }`.
    public static func buildBlock() -> EmptyView

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
    /// through unmodified.
    public static func buildBlock(_ content: Content) -> Content 
      where Content : View
}

Эти @_functionBuilder атрибут является частью неофициальной функции, названной" функциональные разработчики ", который был передан на эволюции Swift здесь и реализовал особенно для версии Swift, который поставлется с Xcode 11, позволяя этому использоваться в SwiftUI.

отмечание типа @_functionBuilder позволяет этому использоваться в качестве пользовательского атрибута на различных объявлениях, таких как функции, вычисленные свойства и, в этом случае, параметры функционального типа. Такие аннотируемые объявления используют функционального разработчика для преобразования блоков кода:

  • Для аннотируемых функций, блок кода, который преобразовывается, является реализацией.
  • Для аннотируемых вычисленных свойств, блок кода, который преобразовывается, является методом считывания.
  • Для аннотируемых параметров функционального типа, блок кода, который преобразовывается, является любым выражением закрытия, которое передается ему (если таковые имеются).

путь, в который функциональный разработчик преобразовывает код, определяется его реализацией [1 140] методы разработчика такой как [1 118], который берет ряд выражений и консолидирует их в единственное значение.

, Например, ViewBuilder реализации buildBlock для 1 - 10 View соответствующие параметры, консолидируя несколько представлений в сингл TupleView:

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
    /// through unmodified.
    public static func buildBlock<Content>(_ content: Content)
       -> Content where Content : View

    public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) 
      -> TupleView<(C0, C1)> where C0 : View, C1 : View

    public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2)
      -> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View

    // ...
}

Это позволяет ряду выражений представления в рамках закрытия, переданного [1 123] инициализатор быть преобразованным в вызов к [1 124], который берет то же количество аргументов. Например:

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

преобразовывается в вызов к [1 125]:

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

получающийся в непрозрачный тип some View результата, удовлетворяемый [1 127].

Вы отметите, что ViewBuilder только определяет buildBlock до 10 параметров, поэтому если мы пытаемся определить 11 подпредставлений:

  var body: some View {
    // error: Static member 'leading' cannot be used on instance of
    // type 'HorizontalAlignment'
    VStack(alignment: .leading) {
      Text("Hello, World")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
    }
  }

мы получаем ошибку компилятора, поскольку нет никакого метода разработчика для обработки этого блока кода (обратите внимание, что, потому что этой функцией является все еще происходящее работой, сообщения об ошибках вокруг этого не будут настолько полезны).

В действительности, я не полагаю, что люди столкнутся с этим ограничением все, что часто, например, вышеупомянутый пример был бы лучше подан с помощью ForEach представление вместо этого:

  var body: some View {
    VStack(alignment: .leading) {
      ForEach(0 ..< 20) { i in
        Text("Hello world \(i)")
      }
    }
  }

однако при необходимости больше чем в 10 статически определенных представлениях Вы можете легко обходное решение это ограничение с помощью Group представление:

  var body: some View {
    VStack(alignment: .leading) {
      Group {
        Text("Hello world")
        // ...
        // up to 10 views
      }
      Group {
        Text("Hello world")
        // ...
        // up to 10 more views
      }
      // ...
    }
<час>

ViewBuilder также реализации другие функциональные методы разработчика такой:

extension ViewBuilder {
    /// Provides support for "if" statements in multi-statement closures, producing
    /// ConditionalContent for the "then" branch.
    public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
      -> ConditionalContent<TrueContent, FalseContent>
           where TrueContent : View, FalseContent : View

    /// Provides support for "if-else" statements in multi-statement closures, 
    /// producing ConditionalContent for the "else" branch.
    public static func buildEither<TrueContent, FalseContent>(second: FalseContent)
      -> ConditionalContent<TrueContent, FalseContent>
           where TrueContent : View, FalseContent : View
}

Это дает ему способность обработать если операторы:

  var body: some View {
    VStack(alignment: .leading) {
      if .random() {
        Text("Hello World!")
      } else {
        Text("Goodbye World!")
      }
      Text("Something else")
    }
  }

, который преобразовывается в:

  var body: some View {
    VStack(alignment: .leading) {
      ViewBuilder.buildBlock(
        .random() ? ViewBuilder.buildEither(first: Text("Hello World!"))
                  : ViewBuilder.buildEither(second: Text("Goodbye World!")),
        Text("Something else")
      )
    }
  }

(испускание избыточного 1 аргумента звонит в [1 133] для ясности).

79
ответ дан 31 October 2019 в 14:19

Аналогичная вещь описана в Новые функции и возможности в Быстром видео WWDC в разделе о DSLs (запускается в ~31:15). Атрибут интерпретируется компилятором и переводится в связанный код:

enter image description here

7
ответ дан 31 October 2019 в 14:19

Другие вопросы по тегам:

Похожие вопросы: