Post

Clean Object Configuration in Swift with `with` and `withCopy`

Two Swift utility functions inspired by Kotlin's scope functions for cleaner object configuration.

Clean Object Configuration in Swift with `with` and `withCopy`

Kotlin’s apply and also scope functions let you configure objects inline without temporary variables. Swift doesn’t have these built-in, but we can create something just as elegant.

The Problem

Consider configuring a UIButton:

1
2
3
4
5
6
let button = UIButton(type: .system)
button.setTitle("Submit", for: .normal)
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 8
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
return button

This works, but configuration is scattered. We need a mutable var or rely on the object being a class. It gets worse when returning from a closure or configuring multiple objects.

The Solution

Two utility functions that change everything:

1
2
3
4
5
6
7
8
9
10
11
12
@discardableResult
public func with<T>(_ object: T, _ configure: (T) throws -> Void) rethrows -> T {
    try configure(object)
    return object
}

@discardableResult
public func withCopy<T>(_ value: T, _ configure: (inout T) throws -> Void) rethrows -> T {
    var copy = value
    try configure(&copy)
    return copy
}

with for Reference Types

Use with when working with classes. The object is passed directly to the closure:

1
2
3
4
5
let label = with(UILabel()) {
    $0.text = "Hello"
    $0.textColor = .systemBlue
    $0.font = .preferredFont(forTextStyle: .headline)
}

Or inline in a lazy property:

1
2
3
4
5
6
lazy var tableView = with(UITableView()) {
    $0.delegate = self
    $0.dataSource = self
    $0.rowHeight = UITableView.automaticDimension
    $0.register(MyCell.self, forCellReuseIdentifier: "cell")
}

withCopy for Value Types

Use withCopy when you need to configure structs. It creates a mutable copy:

1
2
3
4
let rect = withCopy(CGRect.zero) {
    $0.size.width = 100
    $0.size.height = 50
}

Or modifying existing values:

1
2
3
4
let updatedInsets = withCopy(originalInsets) {
    $0.top += 16
    $0.bottom += 16
}

Why Two Functions?

Classes technically conform to both signatures since they can be passed as inout too. Swift’s compiler gets confused with a single function:

1
2
// "Ambiguous use of 'with'"
let params = with(SomeClass()) { $0.property = value }

Separating them with distinct names makes the intent clear and the compiler happy.

Implementation Breakdown

1
2
3
4
5
@discardableResult
public func with<T>(_ object: T, _ configure: (T) throws -> Void) rethrows -> T {
    try configure(object)
    return object
}
  • @discardableResult — Silences warnings when you don’t need the return value
  • <T> — Generic over any type
  • throws / rethrows — The closure can throw, and the error propagates up
  • Returns the same object for chaining or assignment
1
2
3
4
5
6
@discardableResult
public func withCopy<T>(_ value: T, _ configure: (inout T) throws -> Void) rethrows -> T {
    var copy = value
    try configure(&copy)
    return copy
}
  • inout T — Allows mutation of the copied value
  • var copy — Creates a mutable copy of the value type
  • Returns the modified copy

Real-World Example

Here’s how it cleans up a view controller setup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final class ProfileViewController: UIViewController {
    private lazy var stackView = with(UIStackView()) {
        $0.axis = .vertical
        $0.spacing = 16
        $0.alignment = .fill
    }

    private lazy var avatarView = with(UIImageView()) {
        $0.contentMode = .scaleAspectFill
        $0.layer.cornerRadius = 40
        $0.clipsToBounds = true
    }

    private lazy var nameLabel = with(UILabel()) {
        $0.font = .preferredFont(forTextStyle: .title1)
        $0.textAlignment = .center
    }
}

No temporary variables. No scattered configuration. Just clean, readable intent.

☕ Support My Work

If you found this post helpful and want to support more content like this, you can buy me a coffee!

Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏

This post is licensed under CC BY 4.0 by the author.