Result Builder는 프로토콜 @resultBuilder를 통해 구현되는 Swift 속성으로, 일련의 표현식을 복합 값으로 변환합니다. 컴파일러는 제어 구문 if, for, switch가 포함된 코드 블록을 빌더의 정적 메서드 — buildBlock, buildEither, buildArray — 호출로 변환합니다. Swift Evolution 제안 SE-0289(2022)에 따르면, result builders를 사용하면 외부 파서 없이 Swift 내에서 선언적 DSL을 만들 수 있습니다. 가장 유명한 예는 SwiftUI의 @ViewBuilder로, 뷰 본문이 조건부 및 반복 요소로 선언적 스타일로 구성됩니다.
핵심 요점
TupleView로 변환합니다if/else, switch, for-in을 메서드 buildOptional, buildEither, buildArray를 통해 지원합니다Result Builder(이전에는 function builders로 알려짐)는 줄 바꿈으로 구분된 일련의 표현식을 단일 값으로 변환할 수 있는 Swift 메커니즘입니다. 정적 변환 메서드를 구현하는 구조체에 적용되는 @resultBuilder 속성을 사용하여 선언됩니다.
Result builders 이전에는 SwiftUI body의 선언적 구문이 불가능했습니다. 뷰의 간결한 목록 대신 개발자가 수동으로 TupleView 호출을 작성해야 했습니다. Result Builder는 자동으로 각 표현식을 래핑하고, 분기와 루프를 지원하며, 개발자로부터 구성의 복잡성을 숨깁니다.
2022년에 승인된 Swift Evolution SE-0289에 따르면, result builder는 function builders(SE-0258, Swift 5.1) 아이디어의 진화입니다. 주요 변경 사항: @_functionBuilder에서 @resultBuilder로 이름 변경 및 함수 매개변수로의 확장으로, 뷰 본문뿐만 아니라 모든 클로저 인수에 빌더를 사용할 수 있게 되었습니다.
라이브러리 사용자에게 복잡한 구조(구성, 쿼리, UI 구성 요소)를 구축하기 위한 선언적 구문을 제공해야 할 때 Result builders를 사용하세요. 명령형 어셈블리 코드를 작성할 필요가 없습니다.
Swift 컴파일러는 @resultBuilder로 표시된 각 코드 블록을 빌더의 정적 메서드 호출 시퀀스로 변환합니다. 문자열을 연결하는 간단한 빌더를 살펴보겠습니다:
@resultBuilder
struct StringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: " ")
}
}
이 빌더 사용 — 각 문자열이 별도 줄에서 공백으로 연결됩니다:
@StringBuilder
func greeting() -> String {
"Hello"
"World"
"from"
"Swift"
}
// 컴파일러가 이를 변환합니다:
// StringBuilder.buildBlock("Hello", "World", "from", "Swift")
// 결과: "Hello World from Swift"
컴파일러는 연속된 표현식을 그룹화하고 가변 매개변수로 buildBlock에 전달합니다. 표현식 사이에 if가 나타나면 컴파일러는 분기를 위해 buildOptional 또는 buildEither를 호출합니다. for-in 루프의 경우 buildArray를 호출합니다. 이렇게 일반 Swift 코드가 최종 값을 구성하는 호출 체인으로 변환됩니다.
각 result builder는 컴파일러가 변환 중에 호출하는 정적 메서드 집합을 정의합니다. 주요 메서드:
| 메서드 | 목적 | 호출 시점 |
|---|---|---|
| buildBlock | 일련의 표현식을 결합합니다 | 분기가 없는 각 블록에 대해 |
| buildOptional | else 없는 if를 처리합니다 | if가 else 없이 있을 때 |
| buildEither(first:) | if-else의 첫 번째 분기 | if와 else가 있을 때 |
| buildEither(second:) | if-else의 두 번째 분기 | if와 else가 있을 때 |
| buildArray | for-in 루프를 처리합니다 | for-in이 있을 때 |
| buildExpression | 개별 표현식을 변환합니다 | 각 표현식을 buildBlock에 전달하기 전 |
| buildFinalResult | 최종 변환 | 클로저에서 반환하기 전 |
최소 구현에는 가변 매개변수가 있는 buildBlock만 필요합니다 — 분기가 없는 블록에 충분합니다. buildOptional과 buildEither를 추가하면 조건부 구문 지원이 포함되고, buildArray는 루프를 지원합니다. Swift 문서(2025)에 따르면, 최대 DSL 유연성을 위해 모든 메서드를 구현하는 것이 좋습니다.
buildExpression은 다른 유형의 표현식을 받아 단일 빌더 유형으로 변환할 수 있습니다. 예를 들어, @ViewBuilder에서 buildExpression은 Text, Image, Button을 받아 공통 View 유형으로 변환합니다.
HTML 문자열을 구성하는 빌더 생성을 살펴보겠습니다. 이 DSL을 사용하면 Swift에서 직접 선언적으로 HTML을 작성할 수 있습니다:
@resultBuilder
enum HTMLBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined()
}
static func buildOptional(_ component: String?) -> String {
component ?? ""
}
static func buildEither(first component: String) -> String {
component
}
static func buildEither(second component: String) -> String {
component
}
static func buildArray(_ components: [String]) -> String {
components.joined()
}
}
사용자 정의 빌더로 HTML 생성:
func div(@HTMLBuilder _ content: () -> String) -> String {
"<div>\(content())</div>"
}
func p(_ text: String) -> String {
"<p>\(text)</p>"
}
let page = div {
p("Hello")
p("World")
if showFooter {
p("바닥글")
}
}
// 결과: <div><p>Hello</p><p>World</p><p>Footer</p></div>
Swift.org(2025)의 기사 “Building Custom Result Builders in Swift”에 따르면, 사용자 정의 빌더는 구성 파일, UI 구성 요소, 데이터 매핑 및 데이터베이스 쿼리 구축을 위한 라이브러리에서 사용됩니다 — 분기 지원이 있는 선언적 구문이 필요한 모든 곳에서.
@ViewBuilder는 SwiftUI에 내장된 result builder로, 대부분의 컨테이너 뷰의 content 매개변수에 적용됩니다: VStack, HStack, ZStack, Group, List 및 body 속성 자체. 쉼표나 래퍼 없이 여러 뷰를 별도 줄에 작성할 수 있습니다.
@ViewBuilder는 if-else, switch, for-in 지원을 포함한 모든 result builder 메서드를 구현합니다. 조건이 참이면 buildEither(first:)가 하나의 뷰를 반환하고, 거짓이면 buildEither(second:)가 다른 뷰를 반환합니다. 두 분기 모두 동일한 유형을 반환해야 하지만, SwiftUI는 내부적으로 AnyView 또는 ConditionalContent를 통한 유형 소거를 사용합니다.
struct GreetingView: View {
let isLoggedIn: Bool
var body: some View {
VStack {
Image(systemName: "person.circle")
Text("프로필")
.font(.title)
if isLoggedIn {
Text("환영합니다!")
.foregroundColor(.green)
} else {
Button("로그인") { }
}
}
}
}
@ViewBuilder가 없으면 동일한 코드에 각 조건부 섹션에 Group이 필요하거나 AnyView를 사용해야 하며, 이는 성능을 저하시킵니다. @ViewBuilder는 각 조합에 대해 가장 효율적인 표현 — ConditionalContent 또는 TupleView — 을 자동으로 선택합니다.
첫 번째 제한은 buildBlock 내 최대 표현식 수입니다. Swift 표준 라이브러리는 2~10개의 표현식에 대한 buildBlock 오버로드를 정의합니다. 블록에 10개 이상의 표현식이 있으면 컴파일러가 오류를 생성합니다. 해결책은 Group 또는 VStack을 통한 그룹화로 하위 블록으로 나누는 것입니다.
두 번째 제한은 빌더 블록 내에서 변수 및 할당 지원 부족입니다. @ViewBuilder 내에서 let x = 5를 선언할 수 없습니다. 모든 표현식은 빌더 유형의 값을 반환하는 표현식이어야 합니다. 중간 계산의 경우 빌더 외부에서 계산하거나 다른 유형을 지원하는 buildExpression을 사용하세요.
세 번째 제한은 디버깅 복잡성입니다. Result builder 내의 컴파일 오류는 특히 if/else 분기에서 유형이 일치하지 않을 때 혼란스러운 메시지를 생성하는 경우가 많습니다. 디버깅에는 명시적 반환 유형과 AnyView를 사용하세요. 후자는 성능을 저하시킬 수 있습니다. Hacking with Swift(2025)에 따르면, 실용적인 팁은 분기 없는 간단한 빌더로 시작하여 점차 조건부 구문 지원을 추가하는 것입니다.
자주 묻는 질문
Result Builder는 정적 메서드를 통해 일련의 표현식을 결과 값으로 변환하는 Swift 속성입니다. 선언적 DSL을 만들 수 있으며, 가장 유명한 예는 명령형 코드 없이 뷰 계층 구조를 구축하기 위한 SwiftUI의 @ViewBuilder입니다.
@resultBuilder 속성이 있는 구조체를 선언하고 최소한 buildBlock 메서드를 구현합니다. 조건을 지원하려면 buildOptional과 buildEither를 추가하고, 루프에는 buildArray를 추가합니다. 함수의 클로저 매개변수 앞에 빌더 속성을 사용합니다.
buildBlock만 필수입니다. 다른 모든 메서드 — buildOptional, buildEither, buildArray, buildExpression, buildFinalResult — 는 선택 사항이며 해당 구문에 대한 지원을 추가합니다. 더 많은 메서드가 구현될수록 DSL이 더 유연해집니다.
@ViewBuilder는 View 프로토콜에 대한 result builder의 구체적인 구현입니다. SwiftUI에서 @resultBuilder 속성이 있는 구조체로 정의되며, 다양한 수의 뷰에 대한 buildBlock 메서드(TupleView), ConditionalContent에 대한 buildEither 및 ForEach에 대한 buildArray를 제공합니다.
네, 표준 buildBlock 오버로드는 최대 10개의 표현식을 지원합니다. 이를 초과하는 경우 중첩 컨테이너(Group, VStack)를 사용하여 하위 블록으로 나눕니다. 사용자 정의 빌더는 제한 없이 가변 buildBlock을 정의할 수 있습니다.
요약
buildBlock, buildEither, buildOptional, buildArray 호출로 대체합니다턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.