Tüm Yazılar
KategoriSwift
Okuma Süresi
18 dk
Yayın Tarihi
...
Kelime Sayısı
1.793kelime

Kahveni hazırla - bu içerikli bir makale!

Swift Result Builder'lar ile kendi DSL'inizi oluşturun. SwiftUI'ın perde arkasını anlayın, custom HTML builder, query builder ve config DSL örnekleri.

Swift Result Builders: DSL ve Deklaratif API Tasarımı

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

  1. Result Builder Nedir?
  2. SwiftUI Perde Arkası
  3. buildBlock ve Temel Metotlar
  4. buildOptional ve buildEither
  5. buildArray ve Döngüler
  6. Custom HTML Builder
  7. Query Builder DSL
  8. Config Builder
  9. Property Wrapper ile Birleştirme
  10. 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öner
2let value = {
3 let a = 1
4 let b = 2
5 return a + b
6}()
7 
8// Result builder closure - tüm ifadeler toplanır
9let view = VStack {
10 Text("Hello") // buildBlock'a gider
11 Text("World") // buildBlock'a gider
12 // SwiftUI result builder bunları TupleView olarak birleştirir
13}

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 in
8 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") : nil
18 ),
19 ForEach(items) { item in
20 ViewBuilder.buildBlock(Text(item.name))
21 }
22 )
23}

buildBlock ve Temel Metotlar {#build-block}

swift
1@resultBuilder
2struct StringBuilder {
3 // Temel: birden fazla ifadeyi birleştir
4 static func buildBlock(_ components: String...) -> String {
5 components.joined(separator: "\n")
6 }
7 
8 // Tek ifade
9 static func buildExpression(_ expression: String) -> String {
10 expression
11 }
12 
13 // Int'i de kabul et
14 static func buildExpression(_ expression: Int) -> String {
15 String(expression)
16 }
17}
18 
19// Kullanım
20@StringBuilder
21func buildGreeting() -> String {
22 "Hello"
23 "World"
24 42 // Int otomatik String'e çevrilir
25}
26// "Hello\nWorld\n42"

buildOptional ve buildEither {#build-optional}

swift
1@resultBuilder
2struct HTMLBuilder {
3 static func buildBlock(_ components: String...) -> String {
4 components.joined()
5 }
6 
7 // if desteği
8 static func buildOptional(_ component: String?) -> String {
9 component ?? ""
10 }
11 
12 // if-else desteği
13 static func buildEither(first component: String) -> String {
14 component
15 }
16 
17 static func buildEither(second component: String) -> String {
18 component
19 }
20}
21 
22@HTMLBuilder
23func 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ği
3 static func buildArray(_ components: [String]) -> String {
4 components.joined()
5 }
6}
7 
8@HTMLBuilder
9func 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() -> String
3}
4 
5struct HTMLText: HTMLNode {
6 let text: String
7 func render() -> String { text }
8}
9 
10struct HTMLElement: HTMLNode {
11 let tag: String
12 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@resultBuilder
23struct 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@resultBuilder
10struct 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 50

Config Builder {#config-builder}

swift
1struct AppConfig {
2 var apiBaseURL: URL?
3 var timeout: TimeInterval = 30
4 var retryCount: Int = 3
5 var logLevel: LogLevel = .info
6 var features: Set<String> = []
7}
8 
9@resultBuilder
10struct 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 config
17 }
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@propertyWrapper
2struct Validated<Value> {
3 var wrappedValue: Value
4 let validator: (Value) -> Bool
5 
6 init(wrappedValue: Value, validator: @escaping(Value) -> Bool) {
7 self.wrappedValue = wrappedValue
8 self.validator = validator
9 }
10 
11 var projectedValue: Bool { validator(wrappedValue) }
12}
13 
14// Result builder + property wrapper
15struct FormField {
16 let label: String
17 let value: String
18 let isValid: Bool
19}
20 
21@resultBuilder
22struct FormBuilder {
23 static func buildBlock(_ components: FormField...) -> [FormField] { components }
24}
25 
26func form(@FormBuilder _ build: () -> [FormField]) -> [FormField] { build() }

Performans ve Best Practices {#performans}

  1. Compile time: Result builder'lar derleme süresini artırabilir — 10+ ifadede type checker zorlanabilir
  2. buildBlock overload: Maximum 10 parametre (SwiftUI'ın limiti)
  3. buildPartialBlock: Swift 5.7+ ile unlimited ifade desteği
  4. Type erasure: Karmaşık nested tipleri AnyView benzeri 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@resultBuilder
2struct UnlimitedBuilder {
3 // İlk eleman
4 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ği
14 static func buildOptional(_ component: [String]?) -> [String] {
15 component ?? []
16 }
17 
18 // if-else desteği
19 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 verirdi
40 "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)

Etiketler

#swift#result-builders#dsl#metaprogramming#swiftui#declarative
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz