SwiftUI'da her gün kullandığın o güzel deklaratif sözdizimi — VStack { Text("Hello") Image("photo") } — aslında perde arkasında result builder çalışıyor. Bu rehberde, result builder'ların nasıl çalıştığını öğrenecek ve kendi DSL'lerini (Domain-Specific Language) oluşturacaksın.
💡 Hızlı Not: Result builder'lar SE-0289 ile Swift 5.4'te tanıtıldı. SwiftUI, RegexBuilder ve Swift Charts hepsi result builder kullanır.
İçindekiler
- Result Builder Nedir?
- SwiftUI Perde Arkası
- buildBlock ve Temel Metotlar
- buildOptional ve buildEither
- buildArray ve Döngüler
- Custom HTML Builder
- Query Builder DSL
- Config Builder
- Property Wrapper ile Birleştirme
- Performans ve Best Practices
Result Builder Nedir? {#result-builder-nedir}
Result builder, bir closure içindeki ifadeleri özel bir şekilde birleştiren Swift mekanizmasıdır. Normal bir closure sonuç döndürürken, result builder her satırı toplar ve birleştirir:
swift
1// Normal closure - son ifade döner2let value = {3 let a = 14 let b = 25 return a + b6}()7 8// Result builder closure - tüm ifadeler toplanır9let view = VStack {10 Text("Hello") // buildBlock'a gider11 Text("World") // buildBlock'a gider12 // SwiftUI result builder bunları TupleView olarak birleştirir13}SwiftUI Perde Arkası {#swiftui-perde-arkasi}
SwiftUI'ın @ViewBuilder aslında şunu yapar:
swift
1// Sen bu kodu yazarsın:2VStack {3 Text("Hello")4 if showImage {5 Image("photo")6 }7 ForEach(items) { item in8 Text(item.name)9 }10}11 12// Derleyici bunu dönüştürür:13VStack {14 ViewBuilder.buildBlock(15 Text("Hello"),16 ViewBuilder.buildOptional(17 showImage ? Image("photo") : nil18 ),19 ForEach(items) { item in20 ViewBuilder.buildBlock(Text(item.name))21 }22 )23}buildBlock ve Temel Metotlar {#build-block}
swift
1@resultBuilder2struct StringBuilder {3 // Temel: birden fazla ifadeyi birleştir4 static func buildBlock(_ components: String...) -> String {5 components.joined(separator: "\n")6 }7 8 // Tek ifade9 static func buildExpression(_ expression: String) -> String {10 expression11 }12 13 // Int'i de kabul et14 static func buildExpression(_ expression: Int) -> String {15 String(expression)16 }17}18 19// Kullanım20@StringBuilder21func buildGreeting() -> String {22 "Hello"23 "World"24 42 // Int otomatik String'e çevrilir25}26// "Hello\nWorld\n42"buildOptional ve buildEither {#build-optional}
swift
1@resultBuilder2struct HTMLBuilder {3 static func buildBlock(_ components: String...) -> String {4 components.joined()5 }6 7 // if desteği8 static func buildOptional(_ component: String?) -> String {9 component ?? ""10 }11 12 // if-else desteği13 static func buildEither(first component: String) -> String {14 component15 }16 17 static func buildEither(second component: String) -> String {18 component19 }20}21 22@HTMLBuilder23func buildPage(isLoggedIn: Bool) -> String {24 "<html><body>"25 if isLoggedIn {26 "<h1>Welcome back!</h1>"27 } else {28 "<h1>Please login</h1>"29 }30 "</body></html>"31}buildArray ve Döngüler {#build-array}
swift
1extension HTMLBuilder {2 // for-in loop desteği3 static func buildArray(_ components: [String]) -> String {4 components.joined()5 }6}7 8@HTMLBuilder9func buildList(items: [String]) -> String {10 "<ul>"11 for item in items {12 "<li>\(item)</li>"13 }14 "</ul>"15}Custom HTML Builder {#html-builder}
Gerçek bir HTML DSL oluşturalım:
swift
1protocol HTMLNode {2 func render() -> String3}4 5struct HTMLText: HTMLNode {6 let text: String7 func render() -> String { text }8}9 10struct HTMLElement: HTMLNode {11 let tag: String12 let attributes: [String: String]13 let children: [HTMLNode]14 15 func render() -> String {16 let attrs = attributes.map { " \($0.key)=\"\($0.value)\"" }.joined()17 let childrenHTML = children.map { $0.render() }.joined()18 return "<\(tag)\(attrs)>\(childrenHTML)</\(tag)>"19 }20}21 22@resultBuilder23struct HTMLNodeBuilder {24 static func buildBlock(_ components: HTMLNode...) -> [HTMLNode] { components }25 static func buildOptional(_ component: [HTMLNode]?) -> [HTMLNode] { component ?? [] }26 static func buildEither(first: [HTMLNode]) -> [HTMLNode] { first }27 static func buildEither(second: [HTMLNode]) -> [HTMLNode] { second }28 static func buildArray(_ components: [[HTMLNode]]) -> [HTMLNode] { components.flatMap { $0 } }29 static func buildExpression(_ expression: String) -> [HTMLNode] { [HTMLText(text: expression)] }30 static func buildExpression(_ expression: HTMLNode) -> [HTMLNode] { [expression] }31}32 33// DSL fonksiyonları34func div(class className: String? = nil, @HTMLNodeBuilder _ content: () -> [HTMLNode]) -> HTMLElement {35 HTMLElement(tag: "div", attributes: className.map { ["class": $0] } ?? [:], children: content())36}37 38func h1(@HTMLNodeBuilder _ content: () -> [HTMLNode]) -> HTMLElement {39 HTMLElement(tag: "h1", attributes: [:], children: content())40}41 42func p(@HTMLNodeBuilder _ content: () -> [HTMLNode]) -> HTMLElement {43 HTMLElement(tag: "p", attributes: [:], children: content())44}45 46// Kullanım - SwiftUI benzeri sözdizimi!47let page = div(class: "container") {48 h1 { "Hoş Geldiniz" }49 p { "Bu bir Swift DSL ile oluşturuldu!" }50}51print(page.render())52// <div class="container"><h1>Hoş Geldiniz</h1><p>Bu bir Swift DSL ile oluşturuldu!</p></div>Query Builder DSL {#query-builder}
swift
1enum SQLComponent {2 case select([String])3 case from(String)4 case whereClause(String)5 case orderBy(String, ascending: Bool)6 case limit(Int)7}8 9@resultBuilder10struct SQLBuilder {11 static func buildBlock(_ components: SQLComponent...) -> String {12 var parts: [String] = []13 for component in components {14 switch component {15 case .select(let cols): parts.append("SELECT \(cols.joined(separator: ", "))")16 case .from(let table): parts.append("FROM \(table)")17 case .whereClause(let cond): parts.append("WHERE \(cond)")18 case .orderBy(let col, let asc): parts.append("ORDER BY \(col) \(asc ? "ASC" : "DESC")")19 case .limit(let n): parts.append("LIMIT \(n)")20 }21 }22 return parts.joined(separator: " ")23 }24}25 26func query(@SQLBuilder _ build: () -> String) -> String { build() }27 28let sql = query {29 SQLComponent.select(["name", "email", "created_at"])30 SQLComponent.from("users")31 SQLComponent.whereClause("active = true AND age > 18")32 SQLComponent.orderBy("created_at", ascending: false)33 SQLComponent.limit(50)34}35// SELECT name, email, created_at FROM users WHERE active = true AND age > 18 ORDER BY created_at DESC LIMIT 50Config Builder {#config-builder}
swift
1struct AppConfig {2 var apiBaseURL: URL?3 var timeout: TimeInterval = 304 var retryCount: Int = 35 var logLevel: LogLevel = .info6 var features: Set<String> = []7}8 9@resultBuilder10struct ConfigBuilder {11 static func buildBlock(_ components: (inout AppConfig) -> Void...) -> AppConfig {12 var config = AppConfig()13 for component in components {14 component(&config)15 }16 return config17 }18}19 20func apiURL(_ url: String) -> (inout AppConfig) -> Void {21 { $0.apiBaseURL = URL(string: url) }22}23func timeout(_ seconds: TimeInterval) -> (inout AppConfig) -> Void {24 { $0.timeout = seconds }25}26func retryCount(_ count: Int) -> (inout AppConfig) -> Void {27 { $0.retryCount = count }28}29func feature(_ name: String) -> (inout AppConfig) -> Void {30 { $0.features.insert(name) }31}32 33func configure(@ConfigBuilder _ build: () -> AppConfig) -> AppConfig { build() }34 35let config = configure {36 apiURL("https://api.example.com")37 timeout(15)38 retryCount(5)39 feature("dark-mode")40 feature("premium")41}Property Wrapper ile Birleştirme {#property-wrapper}
swift
1@propertyWrapper2struct Validated<Value> {3 var wrappedValue: Value4 let validator: (Value) -> Bool5 6 init(wrappedValue: Value, validator: @escaping(Value) -> Bool) {7 self.wrappedValue = wrappedValue8 self.validator = validator9 }10 11 var projectedValue: Bool { validator(wrappedValue) }12}13 14// Result builder + property wrapper15struct FormField {16 let label: String17 let value: String18 let isValid: Bool19}20 21@resultBuilder22struct FormBuilder {23 static func buildBlock(_ components: FormField...) -> [FormField] { components }24}25 26func form(@FormBuilder _ build: () -> [FormField]) -> [FormField] { build() }Performans ve Best Practices {#performans}
- Compile time: Result builder'lar derleme süresini artırabilir — 10+ ifadede type checker zorlanabilir
- buildBlock overload: Maximum 10 parametre (SwiftUI'ın limiti)
- buildPartialBlock: Swift 5.7+ ile unlimited ifade desteği
- Type erasure: Karmaşık nested tipleri
AnyViewbenzeri wrapper ile sarma
Result Builder Metotları Karşılaştırma Tablosu
Metot | Ne Zaman Çağrılır | Parametre | Döndürdüğü |
|---|---|---|---|
`buildBlock` | Her zaman (temel birleştirme) | Tüm ifadeler | Birleştirilmiş sonuç |
`buildExpression` | Her ifade için (opsiyonel) | Tek ifade | Dönüştürülmüş ifade |
`buildOptional` | `if` (else yok) | Optional component | Component |
`buildEither(first:)` | `if-else` true branch | True branch sonucu | Component |
`buildEither(second:)` | `if-else` false branch | False branch sonucu | Component |
`buildArray` | `for-in` döngüsü | Array of components | Component |
`buildLimitedAvailability` | `if #available` | Component | Component |
`buildFinalResult` | Son dönüş (opsiyonel) | buildBlock sonucu | Final tip |
`buildPartialBlock(first:)` | İlk ifade (Swift 5.7+) | İlk component | Partial result |
`buildPartialBlock(accumulated:next:)` | Sonraki ifadeler | Önceki + yeni | Partial result |
buildPartialBlock ile Sınırsız İfade
Swift 5.7+ ile buildPartialBlock sayesinde SwiftUI'ın 10 view limiti aşılabilir:
swift
1@resultBuilder2struct UnlimitedBuilder {3 // İlk eleman4 static func buildPartialBlock(first: String) -> [String] {5 [first]6 }7 8 // Sonraki elemanlar - sınırsız!9 static func buildPartialBlock(accumulated: [String], next: String) -> [String] {10 accumulated + [next]11 }12 13 // Optional desteği14 static func buildOptional(_ component: [String]?) -> [String] {15 component ?? []16 }17 18 // if-else desteği19 static func buildEither(first: [String]) -> [String] { first }20 static func buildEither(second: [String]) -> [String] { second }21}22 23func list(@UnlimitedBuilder _ build: () -> [String]) -> String {24 build().map { "- \($0)" }.joined(separator: "\n")25}26 27// 10'dan fazla ifade - sorunsuz!28let output = list {29 "Bir"30 "İki"31 "Üç"32 "Dört"33 "Beş"34 "Altı"35 "Yedi"36 "Sekiz"37 "Dokuz"38 "On"39 "On Bir" // buildBlock ile bu HATA verirdi40 "On İki"41}Result Builder Kullanım Alanları
Alan | Örnek Framework | Kullanılan Builder |
|---|---|---|
**UI** | SwiftUI | `@ViewBuilder` |
**Regex** | RegexBuilder | `@RegexComponentBuilder` |
**Grafik** | Swift Charts | `@ChartContentBuilder` |
**Test** | Swift Testing | `@Suite` trait builder |
**HTML** | Custom DSL | `@HTMLNodeBuilder` |
**SQL** | Custom DSL | `@SQLBuilder` |
**Config** | Custom DSL | `@ConfigBuilder` |
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [SE-0289: Result builders](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md) - [SE-0348: buildPartialBlock for result builders](https://github.com/apple/swift-evolution/blob/main/proposals/0348-buildpartialblock.md) - [Apple: ViewBuilder Documentation](https://developer.apple.com/documentation/swiftui/viewbuilder)

