Result Builder — atrybut Swifta, implementowany przez protokół @resultBuilder, który przekształca sekwencję wyrażeń w wartość złożoną. Kompilator przekształca bloki kodu z konstrukcjami sterującymi if, for, switch w wywołania metod statycznych buildera — buildBlock, buildEither, buildArray. Według Swift Evolution proposal SE-0289 (2022), result buildery pozwalają tworzyć deklaratywne DSL w Swift bez zewnętrznych parserów. Najbardziej znany przykład — @ViewBuilder w SwiftUI, gdzie ciało widoku budowane jest z elementów warunkowych i cyklicznych w deklaratywnym stylu.
Najważniejsze
TupleViewif/else, switch, for-in przez metody buildOptional, buildEither, buildArrayResult Builder (wcześniej znany jako function builders) — to mechanizm Swifta, który pozwala przekształcić sekwencję wyrażeń oddzielonych znakiem nowej linii w pojedynczą wartość. Jest deklarowany za pomocą atrybutu @resultBuilder zastosowanego do struktury, która implementuje statyczne metody transformacji.
Przed pojawieniem się result builderów deklaratywna składnia SwiftUI body była niemożliwa. Zamiast zwięzłej listy widoków programista musiałby ręcznie pisać wywołania TupleView. Result Builder automatycznie opakowuje każde wyrażenie, obsługuje rozgałęzienia i pętle, ukrywając złożoność kompozycji przed programistą.
Według Swift Evolution SE-0289, przyjętego w 2022 roku, result builder to rozwinięcie idei function builders (SE-0258, Swift 5.1). Główne zmiany: przemianowanie z @_functionBuilder na @resultBuilder i rozszerzenie na parametry funkcji, co pozwoliło używać builderów dla dowolnych argumentów closure, a nie tylko dla ciała widoku.
Używaj result builderów, gdy chcesz udostępnić użytkownikom swojej biblioteki deklaratywną składnię do budowania złożonych struktur — konfiguracji, zapytań, komponentów UI — bez pisania imperatywnego kodu składania.
Kompilator Swifta przekształca każdy blok kodu oznaczony @resultBuilder w sekwencję wywołań statycznych metod buildera. Rozważmy najprostszy builder, który łączy ciągi znaków:
@resultBuilder
struct StringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: " ")
}
}
Użycie tego buildera — każdy wiersz w osobnej linii łączony jest spacją:
@StringBuilder
func greeting() -> String {
"Hello"
"World"
"from"
"Swift"
}
// Kompilator przekształca to na:
// StringBuilder.buildBlock("Hello", "World", "from", "Swift")
// Wynik: "Hello World from Swift"
Kompilator grupuje kolejne wyrażenia i przekazuje je jako parametr variadic do buildBlock. Jeśli między wyrażeniami występuje if, kompilator wywołuje buildOptional lub buildEither dla rozgałęzienia. Dla pętli for-in wywoływane jest buildArray. W ten sposób zwykły kod Swifta przekształcany jest w łańcuch wywołań konstruujących końcową wartość.
Każdy result builder definiuje zestaw metod statycznych, które kompilator wywołuje podczas transformacji. Podstawowe metody:
| Metoda | Przeznaczenie | Kiedy wywoływana |
|---|---|---|
| buildBlock | Łączy sekwencję wyrażeń | Dla każdego bloku bez rozgałęzień |
| buildOptional | Obsługuje if bez else | Przy wystąpieniu if bez else |
| buildEither(first:) | Pierwsza gałąź if-else | Przy if z else |
| buildEither(second:) | Druga gałąź if-else | Przy if z else |
| buildArray | Obsługuje pętlę for-in | Przy wystąpieniu for-in |
| buildExpression | Przekształca pojedyncze wyrażenie | Dla każdego wyrażenia przed przekazaniem do buildBlock |
| buildFinalResult | Końcowe przekształcenie | Przed zwróceniem z closure |
Minimalna implementacja wymaga tylko buildBlock z parametrami variadic — to wystarcza dla bloków bez rozgałęzień. Dodanie buildOptional i buildEither włącza obsługę konstrukcji warunkowych, a buildArray — pętli. Według Swift Documentation (2025), zaleca się implementację wszystkich metod dla maksymalnej elastyczności DSL.
buildExpression pozwala przyjmować wyrażenia różnych typów i przekształcać je do wspólnego typu buildera. Na przykład w @ViewBuilder buildExpression przyjmuje Text, Image, Button i sprowadza je do wspólnego typu View.
Rozważmy stworzenie buildera do budowania ciągów HTML. Ten DSL pozwoli pisać deklaratywny HTML bezpośrednio w Swifcie:
@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()
}
}
Użycie niestandardowego buildera do generowania 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("Stopka")
}
}
// Wynik: <div><p>Hello</p><p>World</p><p>Footer</p></div>
Według artykułu „Building Custom Result Builders in Swift” ze Swift.org (2025), niestandardowe buildery są używane w bibliotekach do budowania plików konfiguracyjnych, komponentów UI, mapowania danych, a nawet zapytań do bazy danych — wszędzie tam, gdzie potrzebna jest deklaratywna składnia z obsługą rozgałęzień.
@ViewBuilder — to result builder wbudowany w SwiftUI, stosowany do parametru content większości kontenerów: VStack, HStack, ZStack, Group, List oraz samej właściwości body. Pozwala pisać wiele widoków w osobnych wierszach bez przecinków i opakowań.
@ViewBuilder implementuje wszystkie metody result buildera, w tym obsługę if-else, switch i for-in. Gdy warunek jest spełniony, buildEither(first:) zwraca jeden widok; gdy nie — buildEither(second:) zwraca inny. Obie gałęzie muszą zwracać ten sam typ, ale SwiftUI używa AnyView wewnętrznie lub zacierania typu przez ConditionalContent.
struct GreetingView: View {
let isLoggedIn: Bool
var body: some View {
VStack {
Image(systemName: "person.circle")
Text("Profil")
.font(.title)
if isLoggedIn {
Text("Witaj ponownie!")
.foregroundColor(.green)
} else {
Button("Zaloguj się") { }
}
}
}
}
Bez @ViewBuilder ten sam kod wymagałby Group dla każdej sekcji warunkowej lub użycia AnyView, co pogarsza wydajność. @ViewBuilder automatycznie wybiera najefektywniejszą reprezentację — ConditionalContent lub TupleView — dla każdej kombinacji.
Pierwsze ograniczenie — maksymalna liczba wyrażeń w buildBlock. Standardowa biblioteka Swifta definiuje przeciążenia buildBlock dla 2–10 wyrażeń. Jeśli w bloku jest więcej niż 10 wyrażeń, kompilator zgłosi błąd. Rozwiązanie — grupowanie przez Group lub VStack w celu podziału na podbloki.
Drugie ograniczenie — brak wsparcia dla zmiennych i przypisań wewnątrz bloku buildera. Nie można zadeklarować let x = 5 wewnątrz @ViewBuilder. Wszystkie wyrażenia muszą być wyrażeniami zwracającymi wartość typu buildera. Do obliczeń pośrednich używaj obliczeń poza builderem lub buildExpression z obsługą różnych typów.
Trzecie ograniczenie — trudność debugowania. Błędy kompilacji wewnątrz result buildera często dają mylące komunikaty, szczególnie przy niezgodności typów w gałęziach if/else. Używaj jawnych typów zwracanych i AnyView do debugowania, choć ten ostatni obniża wydajność. Według Hacking with Swift (2025), praktyczna rada — zaczynaj od prostego buildera bez rozgałęzień i stopniowo dodawaj obsługę konstrukcji warunkowych.
Często zadawane pytania
Result Builder — atrybut Swifta, przekształcający sekwencję wyrażeń w wartość wynikową przez statyczne metody. Pozwala tworzyć deklaratywne DSL, najbardziej znany przykład — @ViewBuilder w SwiftUI do budowania hierarchii widoków bez kodu imperatywnego.
Zadeklaruj strukturę z atrybutem @resultBuilder i zaimplementuj co najmniej metodę buildBlock. Aby obsługiwać warunki, dodaj buildOptional i buildEither, dla pętli — buildArray. Użyj atrybutu buildera przed parametrem closure w funkcji.
Obowiązkowy jest tylko buildBlock. Wszystkie pozostałe metody — buildOptional, buildEither, buildArray, buildExpression, buildFinalResult — są opcjonalne i dodają obsługę odpowiednich konstrukcji. Im więcej metod zaimplementowano, tym elastyczniejszy DSL.
@ViewBuilder — to konkretna implementacja result buildera dla protokołu View. Jest zdefiniowany w SwiftUI jako struktura z atrybutem @resultBuilder, dostarczająca metody buildBlock dla różnych liczb widoków (TupleView), buildEither dla ConditionalContent i buildArray dla ForEach.
Tak, standardowe przeciążenia buildBlock obsługują do 10 wyrażeń. Po przekroczeniu używaj zagnieżdżonych kontenerów (Group, VStack) do podziału na podbloki. Niestandardowy builder może zdefiniować variadic buildBlock bez ograniczenia.
Podsumowanie
buildBlock, buildEither, buildOptional, buildArray w zależności od konstrukcji sterującychOpracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również