The Builder Pattern is a creational design pattern designed to separate the construction of a complex object from its representation.

In classic Gang of Four (GoF) object-oriented languages like Java, the Builder is essential for avoiding the “telescoping constructor” anti-pattern (where you have a dozen different initializers for every combination of parameters).

However, in modern Swift, the classic Builder pattern is often redundant. Swift’s native features—like named parameters, default values, and structs—solve the telescoping constructor problem out of the box. Therefore, when we use the Builder pattern in Swift today, it usually takes on one of three highly optimized, modern forms.

Here are the three ways the Builder pattern is practically applied in modern iOS architecture.

1. The Fluent Builder (Method Chaining)

This is closest to the classic GoF pattern but uses Self to allow for elegant method chaining. It is incredibly useful for constructing complex configurations, such as Network Requests or Analytics payloads, where the construction process needs to be readable and step-by-step.

class URLRequestBuilder {
    private var url: URL?
    private var method: String = "GET"
    private var headers: [String: String] = [:]
    private var body: Data?

    func set(url: URL) -> Self {
        self.url = url
        return self
    }

    func set(method: String) -> Self {
        self.method = method
        return self
    }

    func add(header: String, value: String) -> Self {
        self.headers[header] = value
        return self
    }

    func build() throws -> URLRequest {
        guard let url = url else { throw URLError(.badURL) }
        var request = URLRequest(url: url)
        request.httpMethod = method
        request.allHTTPHeaderFields = headers
        request.httpBody = body
        return request
    }
}

// Usage:
let request = try URLRequestBuilder()
    .set(url: URL(string: "https://api.example.com/v1/users")!)
    .set(method: "POST")
    .add(header: "Authorization", value: "Bearer token")
    .build()

2. The Closure-Based Builder (Configuration Block)

This pattern is heavily used when migrating legacy UIKit codebases or setting up complex object graphs without subclassing. It allows you to instantiate and configure an object in a single, clean scope.

protocol Buildable {}
extension Buildable where Self: AnyObject {
    func configure(_ closure: (Self) -> Void) -> Self {
        closure(self)
        return self
    }
}

extension NSObject: Buildable {}

// Usage (Common in programmatic UIKit):
let titleLabel = UILabel().configure {
    $0.text = "System Architecture"
    $0.font = .preferredFont(forTextStyle: .headline)
    $0.textColor = .label
    $0.translatesAutoresizingMaskIntoConstraints = false
}

3. The Ultimate Swift Builder: @resultBuilder

Since Swift 5.4, the language has first-class support for creating custom DSLs (Domain-Specific Languages) via Result Builders. This is the exact pattern that powers SwiftUI’s ViewBuilder. It allows you to build complex trees of objects using native Swift syntax (if/else, loops) without commas or explicit array brackets.

If you are designing a complex modular system, creating your own Result Builder is the most modern approach.

// 1. Define the components
protocol Component {
    var content: String { get }
}
struct TextComponent: Component { var content: String }
struct SpacerComponent: Component { var content: String = "\n---\n" }

// 2. Create the Result Builder
@resultBuilder
struct DocumentBuilder {
    static func buildBlock(_ components: Component...) -> [Component] {
        return components
    }
    
    static func buildOptional(_ component: [Component]?) -> [Component] {
        return component ?? []
    }
}

// 3. The Director / Consumer
struct Document {
    var components: [Component]
    
    init(@DocumentBuilder _ builder: () -> [Component]) {
        self.components = builder()
    }
    
    func render() {
        components.forEach { print($0.content) }
    }
}

// Usage:
let showHeader = true

let myDoc = Document {
    TextComponent(content: "Title: iOS Architecture")
    SpacerComponent()
    
    if showHeader {
        TextComponent(content: "Author: Abhishek Shukla")
    }
    
    TextComponent(content: "Body: The builder pattern in Swift...")
}

When to use which?

  • Use Fluent Builders for configuration objects that require validation before creation (like network requests).
  • Use Closure Builders for cleaning up setup code for UI components or classes you don’t own.
  • Use @resultBuilder when you are building a hierarchical structure (like a custom UI framework, a routing graph, or an HTML/Markdown generator for your new website!).