Skip to content

Builder ​

Builder separates the steps for configuring an object from the finished object. It is useful when a feature has required inputs and several independent options.

When it helps in iOS ​

Imagine a share sheet that can include a title, message, link, and optional tracking source. Call sites may need different combinations, but every share must have a valid link. A builder can validate that rule once.

Example: assembling a share payload ​

swift
import Foundation

struct SharePayload {
    let title: String
    let message: String?
    let url: URL
}

struct SharePayloadBuilder {
    private var title = ""
    private var message: String?
    private var url: URL?

    func titled(_ value: String) -> Self {
        var copy = self
        copy.title = value
        return copy
    }

    func withMessage(_ value: String) -> Self {
        var copy = self
        copy.message = value
        return copy
    }

    func linkingTo(_ value: URL) -> Self {
        var copy = self
        copy.url = value
        return copy
    }

    func build() throws -> SharePayload {
        guard let url else { throw BuildError.missingURL }
        return SharePayload(title: title, message: message, url: url)
    }

    enum BuildError: Error { case missingURL }
}

let payload = try SharePayloadBuilder()
    .titled("Weekend itinerary")
    .withMessage("Here is the route we planned.")
    .linkingTo(URL(string: "https://example.com/trips/42")!)
    .build()

The builder is a value type, so each step returns a configured copy. The final build() call enforces the required URL.

How to use it in a real app ​

Convert the payload into activity items at the presentation edge, then pass those items to UIActivityViewController. This keeps UIKit presentation out of the model. In app code, prefer a known URL from your router instead of force-unwrapping a string as this short example does.

Watch for: If the type has only two or three clear fields, a normal initializer with defaults is easier to read. Use a builder when optional combinations or validation make call sites confusing.

Trace the decision ​

The required invariant is the URL. Every fluent setter returns a new builder value, so the same base builder can produce multiple payloads without shared mutation. build() is the one place that rejects an incomplete payload. A production builder could also reject an empty title and normalize tracking parameters.

A builder becomes valuable when configuration has a meaningful sequence or validation. If every call site always supplies the same three fields, a SharePayload initializer is shorter and easier to audit. Test the missing-URL path as well as the successful path.

Read the construction sequence ​

Each setter takes self, copies the value, changes one field, and returns the copy. This produces fluent call sites without mutable shared state. build() is the boundary where an incomplete builder becomes either a valid SharePayload or an error. The URL is optional inside the builder because construction is in progress, but non-optional inside the finished payload. That distinction is why the builder can enforce an invariant.

From example to an iOS screen ​

A trip detail view model can build a payload after the user taps Share. The view then maps the payload to activity items and presents UIActivityViewController. Other call sites may omit the message or use a different title without adding several overloaded initializers. For universal links, construct the URL from a trusted route builder; do not force-unwrap user input. If analytics needs a campaign parameter, add it when creating the URL rather than hiding it in the share sheet.

Failure modes and verification ​

Test missing required fields, each optional combination, and whether reusing a base builder leaks settings between payloads. A builder with many meaningless setters is a sign that the finished object needs a clearer model. When a struct has only a few independent properties and no validation, an initializer with defaults is simpler.

Next: Singleton →

Released under the MIT License.