Result Builder — атрибут Swift, що реалізується через протокол @resultBuilder, який трансформує послідовність виразів у складове значення. Компілятор перетворює блоки коду з керуючими конструкціями if, for, switch у виклики статичних методів білдера — buildBlock, buildEither, buildArray. Згідно з Swift Evolution proposal SE-0289 (2022), result builders дозволяють створювати декларативні DSL всередині Swift без зовнішніх парсерів. Найвідоміший приклад — @ViewBuilder у SwiftUI, де тіло view будується з умовних і циклічних елементів у декларативному стилі.
Головне
TupleViewif/else, switch, for-in через методи buildOptional, buildEither, buildArrayResult Builder (раніше відомий як function builders) — це механізм Swift, який дозволяє перетворювати послідовність виразів, розділених переносом рядка, на єдине значення. Він оголошується за допомогою атрибута @resultBuilder, застосованого до структури, яка реалізує статичні методи перетворення.
До появи result builders декларативний синтаксис SwiftUI body був неможливий. Замість компактного списку view розробнику довелося б писати виклики TupleView вручну. Result Builder автоматично обгортає кожен вираз, підтримує розгалуження та цикли, приховуючи складність композиції від розробника.
Згідно з Swift Evolution SE-0289, прийнятим у 2022 році, result builder — це розвиток ідеї function builders (SE-0258, Swift 5.1). Основні зміни: перейменування з @_functionBuilder на @resultBuilder і розширення на параметри функцій, що дозволило використовувати білдери для будь-яких closure-аргументів, а не тільки для body view.
Використовуйте result builders, коли потрібно надати користувачам вашої бібліотеки декларативний синтаксис для побудови складних структур — конфігурацій, запитів, UI-компонентів — без написання імперативного коду збірки.
Компілятор 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 | Обробляє if без else | За наявності if без else |
| buildEither(first:) | Перша гілка if-else | При if з else |
| buildEither(second:) | Друга гілка if-else | При if з else |
| buildArray | Обробляє цикл for-in | За наявності for-in |
| buildExpression | Перетворює окремий вираз | Для кожного виразу перед передачею в buildBlock |
| buildFinalResult | Фінальне перетворення | Перед поверненням із closure |
Мінімальна реалізація вимагає тільки buildBlock з variadic параметрами — цього достатньо для блоків без розгалужень. Додавання buildOptional та buildEither включає підтримку умовних конструкцій, а buildArray — циклів. Згідно з документацією Swift (2025), рекомендується реалізовувати всі методи для максимальної гнучкості DSL.
buildExpression дозволяє приймати вирази різних типів і перетворювати їх до єдиного типу білдера. Наприклад, у @ViewBuilder buildExpression приймає Text, Image, Button і приводить їх до спільного типу View.
Розглянемо створення білдера для побудови HTML-рядків. Цей DSL дозволить писати декларативний HTML прямо в Swift:
@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>
Згідно зі статтею «Building Custom Result Builders in Swift» від Swift.org (2025), кастомні білдери застосовуються в бібліотеках для побудови конфігураційних файлів, UI-компонентів, маппінгу даних і навіть запитів до бази даних — усюди, де потрібен декларативний синтаксис із підтримкою розгалужень.
@ViewBuilder — це result builder, вбудований у SwiftUI, який застосовується до параметра content більшості контейнерних view: VStack, HStack, ZStack, Group, List і самої властивості body. Він дозволяє писати кілька view на окремих рядках без ком і обгорток.
@ViewBuilder реалізує всі методи result builder, включаючи підтримку if-else, switch та for-in. Коли умова виконується, buildEither(first:) повертає одну view; коли ні — 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 визначає перевантаження buildBlock для 2–10 виразів. Якщо в блоці більше 10 виразів, компілятор видасть помилку. Рішення — групування через Group або VStack для розбиття на підблоки.
Друге обмеження — відсутність підтримки змінних і присвоєнь всередині білдер-блоку. Не можна оголосити let x = 5 всередині @ViewBuilder. Усі вирази мають бути виразами, що повертають значення типу білдера. Для проміжних обчислень використовуйте обчислення поза білдером або buildExpression з підтримкою різних типів.
Третє обмеження — складність налагодження. Помилки компіляції всередині result builder часто дають заплутані повідомлення, особливо при невідповідності типів у гілках if/else. Використовуйте явні типи, що повертаються, і AnyView для налагодження, хоча останній знижує продуктивність. Згідно з Hacking with Swift (2025), практична порада — починати з простого білдера без розгалужень і додавати підтримку умовних конструкцій поступово.
Часті запитання
Result Builder — атрибут Swift, що перетворює послідовність виразів у результуюче значення через статичні методи. Дозволяє створювати декларативні DSL, найвідоміший приклад — @ViewBuilder у SwiftUI для побудови ієрархії view без імперативного коду.
Оголосіть структуру з атрибутом @resultBuilder і реалізуйте метод buildBlock мінімум. Для підтримки умов додайте buildOptional і buildEither, для циклів — buildArray. Використовуйте атрибут білдера перед параметром closure у функції.
Обов'язковий тільки buildBlock. Всі інші методи — buildOptional, buildEither, buildArray, buildExpression, buildFinalResult — опціональні та додають підтримку відповідних конструкцій. Чим більше методів реалізовано, тим гнучкіший DSL.
@ViewBuilder — це конкретна реалізація result builder для протоколу View. Він визначений у SwiftUI як структура з атрибутом @resultBuilder, що надає методи buildBlock для різних кількостей view (TupleView), buildEither для ConditionalContent і buildArray для ForEach.
Так, стандартні перевантаження buildBlock підтримують до 10 виразів. При перевищенні використовуйте вкладені контейнери (Group, VStack) для розбиття на підблоки. Кастомний білдер може визначити variadic buildBlock без обмеження.
Підсумки
buildBlock, buildEither, buildOptional, buildArray залежно від керуючих конструкційМи розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.