[swift] SwiftUI의 DSL을 활성화하는 것은 무엇입니까?

Apple의 새로운 SwiftUI프레임 워크는 효과적으로 튜플을 빌드하는 새로운 종류의 구문 을 사용 하지만 다른 구문이있는 것 같습니다.

var body: some View {
    VStack(alignment: .leading) {
        Text("Hello, World") // No comma, no separator ?!
        Text("Hello World!")
    }
}

이 구문이 실제로 무엇인지 다루려고 시도하면서VStack 여기에 사용 된 이니셜 라이저가 유형의 클로저를 () -> Content
두 번째 매개 변수로 취 한다는 것을 알았습니다. 여기에서 클로저를 통해 추론 Content되는 일반 매개 변수는 이에 부합합니다 View. 어떤 유형 Content이 추론 되는지 알아보기 위해 기능을 유지하면서 코드를 약간 변경했습니다.

var body: some View {
    let test = VStack(alignment: .leading) {
        Text("Hello, World")
        Text("Hello World!")
    }

    return test
}

이와 함께, test유형으로 자신을 밝혀 VStack<TupleView<(Text, Text)>>그 의미 Content유형입니다 TupleView<Text, Text>. 를 찾아 보면 TupleView, SwiftUI래핑해야하는 튜플을 전달해야만 초기화 할 수있는 자체 에서 시작된 래퍼 유형이라는 것을 알았습니다 .

질문

이제이 Text예제 의 두 인스턴스가 어떻게 TupleView<(Text, Text)>. 이 해킹되어 SwiftUI있으므로 잘못된 정규 스위프트 구문? 유형이 TupleView되는 것은 SwiftUI이 가정을 뒷받침합니다. 아니면 이것이 유효한 Swift 구문입니까? 그렇다면 외부 에서 어떻게 사용할 수 있습니까?SwiftUI



답변

마틴 말했듯이 당신에 대한 설명서를 보면, VStackinit(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의 진화에 대해 언급했으며 Xcode 11과 함께 제공되는 Swift 버전을 위해 특별히 구현되어 SwiftUI에서 사용할 수 있습니다.

유형을 표시하면 @_functionBuilder함수, 계산 된 속성 및이 경우 함수 유형의 매개 변수와 같은 다양한 선언에서 사용자 정의 속성으로 사용할 수 있습니다. 이러한 주석이 달린 선언은 함수 빌더를 사용하여 코드 블록을 변환합니다.

  • 주석이 달린 함수의 경우 변환되는 코드 블록이 구현입니다.
  • 주석이 달린 계산 된 속성의 경우 변환되는 코드 블록이 게터입니다.
  • 함수 유형의 주석이 달린 매개 변수의 경우 변환되는 코드 블록은 전달되는 클로저 표현식입니다 (있는 경우).

함수 빌더가 코드를 변환하는 방식은 일련의 표현식을 가져와 단일 값으로 통합하는와 같은 빌더 메소드 의 구현에 의해 정의됩니다 buildBlock.

예를 들어 1 ~ 10 개의 준수 매개 변수 를 ViewBuilder구현 buildBlock하여 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

    // ...
}

이를 통해 VStack의 이니셜 라이저에 전달 된 클로저 내의 뷰 표현식 세트를 buildBlock동일한 수의 인수 를 사용하는 호출로 변환 할 수 있습니다 . 예를 들면 :

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

에 대한 호출로 변환됩니다 buildBlock(_:_:).

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

결과 은폐하는 결과 유형 some View 에 의해 충족되고 TupleView<(Text, Text)>.

당신은 점에 유의하겠습니다 ViewBuilder만 정의 buildBlock우리가 11 파단을 정의하려고 시도 그렇다면, 10 개의 매개 변수를 :

  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
}

이를 통해 if 문을 처리 할 수 ​​있습니다.

  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")
      )
    }
  }

( ViewBuilder.buildBlock명확성 을 위해 중복 1 인수 호출을 냄).


답변

유사한 내용은 DSL에 관한 섹션의 Swift WWDC 비디오의 새로운 기능 (~ 31 : 15에서 시작)에 설명되어 있습니다. 이 속성은 컴파일러에 의해 해석되고 관련 코드로 변환됩니다.

여기에 이미지 설명 입력


답변