본문 바로가기

카테고리 없음

[SwiftUI] Swift 문법

https://github.com/swiftlang/swift-evolution/blob/main/proposals/0255-omit-return.md

 

swift-evolution/proposals/0255-omit-return.md at main · swiftlang/swift-evolution

This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swiftlang/swift-evolution

github.com

Omit Return (리턴 생략)

단일 표현식 리턴 생략

 


 

https://github.com/swiftlang/swift-evolution/blob/main/proposals/0244-opaque-result-types.md

Opaque Type (애매모호한 반환 타입)

반환하는 타입이 구체적으로 무엇인지는 숨기고, 그 타입이 특정 프로토콜을 준수한다는 사실 만을 공개하는 방식이다.

타입에 some 키워드를 붙여서 사용할 수 있으며, 프로퍼티나 첨자, 함수 등의 반환 타입에 사용된다.

 

제안의 배경에는 타입 추상화를 위해 Generic 모델의 UI 개선을 위한다고 나와있다.

제안자는 Generic이 오버헤드를 유발하고, 클라이언트가 명확한 타입을 알려줘야 한다는 점이 개선점이라고 지적한다.

Opaque Type을 사용하면 컴파일러만 정확한 타입 정보에 접근하고, 모듈의 클라이언트는 정확한 타입에 대해서 몰라도 된다.

 

Generic은 타입을 매개변수로 받아서 구체적인 타입을 지정하지 않고 유연하게 다룰 수 있으나 호출 시 타입을 명확히 지정해줘야 한다.

Opqaue Return type은 반환 타입이 구체적으로 무엇인지 알 수 없고, 해당 타입이 특정 프로토콜을 준수한다고만 명시한다.

코드로 보면...

// Generic
protocol GameObject {
    associatedtype Shape: ShapeProtocol
    var shape: Shape { get }
}

struct EightPointedStar: GameObject {
    var shape: Union<Rectangle, Transformed<Rectangle>> {
        return Union(Rectangle(), Transformed(Rectangle(), by: .fortyFiveDegrees))
    }
}

// Opaque Return Type
protocol GameObject {
    var shape: some Shape { get }
}

struct EightPointedStar: GameObject {
    var shape: some Shape {
        return Union(Rectangle(), Transformed(Rectangle(), by: .fortyFiveDegrees))
    }
}

 

EightPointedStar의 타입에 매우 긴 타입이 적혀있는것을 볼 수 있다.

 

만약에 SwiftUI의 body가 some View가 아닌 그냥 View 였다면..?

var body: ModifiedContent<Text, _BackgroundModifier<Color>> { .. }

 

이런식으로 중첩되어 계속 되었을 것이다.

 


 

Result Builder

Result Builder는 Swift 5.4에서 도입된 용어로, 이전 실험버전에서는 @_functionBuilder이다.

사용자 정의 도메인 언어(DSL)을 만들기 위해 사용된다.

 

사용자 정의 DSL(Domain-Specific Language)란

특정 목적(도메인)에 최적화된 작고 간결한 프로그래밍 언어를 말한다.

Swift에서 DSL은 보통 깔끔하고 읽기 좋은 코드를 작성하기 위해 사용된다.

 

예를들어...

VStack의 사용법은 매우 간단하다.

VStack {
    Text("Hello")
    Text("World")
}

 

이 자체가 Swift를 기반으로 만든 사용자 정의 DSL이다.

VStack 안의 중괄호({}) 내용은 Swift 문법처럼 보이지만, 사실은 UI 레이아웃을 만들기 위한 DSL인 것!

@resultBuilder는 이런 DSL을 만들 때 사용하는 도구이다.

 

어떤식으로..?

VStack의 생성자에서 @ViewBuilder라는 것을 볼 수 있다.

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct VStack<Content> : View where Content : View {

    @inlinable public init(alignment: HorizontalAlignment = .center,
    					   spacing: CGFloat? = nil,
                           @ViewBuilder content: () -> Content)
                           
    public typealias Body = Never
}

 

이 @ViewBuilder라는 것의 정의를 보면...

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@resultBuilder public struct ViewBuilder {

}

 

@resultBuilder 속성이 붙은것을 확인할 수 있다.

즉, @ViewBuilder는 @resultBuilder 속성을 이용해서 만들어진 사용자 정의 DSL이다.

 

@ViewBuilder

뷰 빌더는 함수로 정의된 매개 변수에 View를 전달 받아 하나 이상의 자식 View를 만들어내는 기능을 수행한다.

 

1개 이상의 자식 뷰를 리턴하는 문법이라고 생각하자.

 

그래서 VStack을 만들 때 @ViewBuilder 속성을 적용한 content에 단지 View를 나열하는것 만으로도, 쉽게 여러개의 자식 View를 가진 컨테이너 View가 만들어졌다.

 

 

@ViewBuilder의 동작 방식

ViewBuilder는 buildBlock이라는 타입 메서드에 값을 전달하고, 2개 이상의 View일 때는 TupleView라는 타입을 반환한다.

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@resultBuilder public struct ViewBuilder {

    /// Builds an expression within the builder.
    public static func buildExpression<Content>(_ content: Content) -> Content where Content : View

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

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

    public static func buildBlock<each Content>(_ content: repeat each Content) -> TupleView<(repeat each Content)> where repeat each Content : View
}

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

    /// Produces an optional view for conditional statements in multi-statement
    /// closures that's only visible when the condition evaluates to true.
    public static func buildIf<Content>(_ content: Content?) -> Content? where Content : View

    /// Produces content for a conditional statement in a multi-statement closure
    /// when the condition is true.
    public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View

    /// Produces content for a conditional statement in a multi-statement closure
    /// when the condition is false.
    public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
}

 

이 코드가 실제로는 ViewBuilder 내부에서

 

struct ViewBuilderTestView: View {
    var body: some View {
        VStack {
            Text("1")
            Bool.random() ? Spacer() : Divider()
            if Bool.random() {
                Text("2")
            }
        }
    }
}

 

이렇게 작동된다!

struct ViewBuilderTestView: View {
    var body: some View {
        VStack {
            ViewBuilder.buildBlock {
                Text("1")
                Bool.random() ? ViewBuilder.buildEither(first: Spacer()) :                 ViewBuilder.buildEither(second: Divider())
                ViewBuilder.buildIf(Bool.random() ? Text("2") : nil)
            }
        }
    }
}

 

Custom ResultBuilder 만드는건 다음 기회에..