Articles, podcasts and news about Swift development, by John Sundell.

Static factory methods in Swift

Published on 03 Jun 2018

Most objects require some form of setup before they're ready to be used in an app. Whether it's a view that we want to style according to our app's brand, a view controller that we're configuring, or when creating stubbed values in a test - we often find the need to put setup code somewhere.

A very common place to put such setup code is in a subclass. Simply subclass the object you need to setup, override its initializer and perform the setup there - done! While that is certainly a way to do it, this week, let's take a look at an alternative approach to writing setup code that doesn't require any form of subclassing - by using static factory methods.

Views

One of the most common objects that we have to setup when writing UI code is views. Both UIKit on iOS and AppKit on the Mac give us all the essential, core building blocks that we need to create a UI with a native look and feel - but we often need to customize those looks to fit our design and define layout for them.

Again, this is where many developers will opt for subclassing, and create custom variants of the built-in view classes - like here for a label that we'll use to render a title:

class TitleLabel: UILabel {
    override init(frame: CGRect) {
        super.init(frame: frame)

        font = .boldSystemFont(ofSize: 24)
        textColor = .darkGray
        adjustsFontSizeToFitWidth = true
        minimumScaleFactor = 0.75
    }
}

There's nothing really wrong with the above approach, but it does create more types to keep track of, and we also often end up with multiple subclasses for slight variants of the same type of view (like TitleLabel, SubtitleLabel, FeaturedTitleLabel, etc).

While subclassing is an important language feature, even in the age of Protocol-Oriented Programming, it's easy to confuse custom setup with custom behavior. We're not really adding any new behavior to UILabel above, we're just setting an instance up. So the question is whether a subclass is really the right tool for the job here?

Let's instead try to use a static factory method to achieve the same thing. What we'll do is add an extension on UILabel that enables us to create a new instance with the same exact setup as TitleLabel from above, like this:

extension UILabel {
    static func makeForTitle() -> UILabel {
        let label = UILabel()
        label.font = .boldSystemFont(ofSize: 24)
        label.textColor = .darkGray
        label.adjustsFontSizeToFitWidth = true
        label.minimumScaleFactor = 0.75
        return label
    }
}

The beauty of the above approach (besides it not relying on subclassing or adding any new types), is that we are clearly separating our setup code from our actual logic. Also, since extensions can be scoped to a single file (by adding private) we can easily set up extensions for parts of our app that need to create specific views only a one single feature:

// We'll only use this in a single view controller, so we'll scope
// it as private (for now) as to not add this functionality to
// UIButton globally in our app.
private extension UIButton {
    static func makeForBuying() -> UIButton {
        let button = UIButton()
        ...
        return button
    }
}

Using the above static factory method approach, we can now make our UI code look pretty nice, since all we have to do is call our methods to create fully configured instances for what we need:

class ProductViewController {
    private lazy var titleLabel = UILabel.makeForTitle()
    private lazy var buyButton = UIButton.makeForBuying()
}

If we want to make our APIs even more minimalistic (which in many ways Swift encourages with features like dot syntax and how it shortens imported Objective-C APIs), we can even turn our methods into a computed properties, like this:

extension UILabel {
    static var title: UILabel {
        let label = UILabel()
        ...
        return label
    }
}

Which makes the call site even more simple and clean:

class ProductViewController {
    private lazy var titleLabel = UILabel.title
    private lazy var buyButton = UIButton.buy
}

Of course, if we end up adding arguments to our setup APIs, we'll need to turn them back into methods - but using static computed properties this way can be a pretty nice option for simpler use cases.

View controllers

Let's move on to view controllers, another kind of objects that are very common to use subclasses for. While we probably won't be able to get rid of subclassing completely for view controllers (or views for that matter), there are certain kinds of view controllers that could benefit from the factory approach.

Especially when using child view controllers, we often end up with a group of view controllers that are only there to render a certain state - rather than having lots of logic in them. For those view controllers, moving their setup to a static factory API can be a pretty nice solution.

Here we are using that approach to implement a computed property that returns a loading view controller that we'll use to present a loading spinner:

extension UIViewController {
    static var loading: UIViewController {
        let viewController = UIViewController()

        let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
        indicator.translatesAutoresizingMaskIntoConstraints = false
        indicator.startAnimating()
        viewController.view.addSubview(indicator)

        NSLayoutConstraint.activate([
            indicator.centerXAnchor.constraint(
                equalTo: viewController.view.centerXAnchor
            ),
            indicator.centerYAnchor.constraint(
                equalTo: viewController.view.centerYAnchor
            )
        ])

        return viewController
    }
}

As you can see above, we can even set up internal Auto Layout constraints within our static properties or functions. This is a situation in which the declarative nature of Auto Layout really comes in handy - we can simply specify all of our constraints up front, without having to override any methods or respond to any calls.

Like when used for views, the factory approach gives us very nice and clean call sites. Especially if combined with a slightly modified version of the convenience API from "Using child view controllers as plugins in Swift", we can now easily add a pre-configured loading view controller when performing an asynchronous operation:

class ProductListViewController: UIViewController {
    func loadProducts() {
        let loadingVC = add(.loading)

        productLoader.loadProducts { [weak self] result in
            loadingVC.remove()
            self?.handle(result)
        }
    }
}

The only modification made to the add convenience API is making it return the added child view controller, making it possible to get a reference to it while using dot syntax. Adding @discardableResult as well also removes any warnings when that new feature is not being used.

Test stubs

It's not only in our main app code that we have to perform lots of setup, we also often have to do it when writing tests. Especially when testing code that relies on specific model configurations, it's easy to end up with tests that are full of boilerplate - making them much harder to read and debug.

Let's say we have a User model in our app that contains what kind of permissions a given user has, and that a lot of our tests are about verifying our logic based on the current user's permissions. Instead of having to manually create user values with boilerplate data in all of our tests, let's create a static factory method that returns a user stub based on a set of permissions, like this:

extension User {
    static func makeStub(permissions: Set<User.Permission>) -> User {
        return User(
            name: "TestUser",
            age: 30,
            signUpDate: Date(),
            permissions: permissions
        )
    }
}

We can now get rid of any User setup code, allowing us to focus on what we're actually testing - like here where we're verifying that a user with the deleteFolders permission is able to delete a folder:

class FolderManagerTests: XCTestCase {
    func testDeletingFolder() throws {
        // We can now quickly create a user with the required permissions
        let user = User.makeStub(permissions: [.deleteFolders])
        let manager = FolderManager(user: user)
        let folderName = "Test"

        try manager.addFolder(named: folderName)
        XCTAssertNotNil(manager.folder(named: folderName))

        try manager.deleteFolder(named: folderName)
        XCTAssertNil(manager.folder(named: folderName))
    }
}

As our test suite grows and we start verifying more things involving User models, we might also need to set additional properties when creating stubs. An easy way to add support for that, without having to create additional methods, is to use default arguments:

extension User {
    static func makeStub(age: Int = 30,
                         permissions: Set<User.Permission> = []) -> User {
        return User(
            name: "TestUser",
            age: age,
            signUpDate: Date(),
            permissions: permissions
        )
    }
}

We are now free to either supply an age, a set of permissions, or both - and we can even easily create a blank user with User.makeStub() in case what we're testing is not relying on any specific user state.

By naming the above factory method makeStub we also make it crystal clear that this code is only meant for testing, so that it doesn't accidentally get added to our main app target in the future.

Conclusion

Using static factory methods and properties to perform setup of objects can be a great way to clearly separate setup code from actual logic, to enable nice syntax features and to make it easier to write clean test code.

While subclassing still is an important tool to have in our toolbox - especially when we want to actually add logic to a type - getting rid of subclasses that really only perform configuration can make our code base easier to navigate and reduce the number of types we have to maintain.

An alternative to using static factory methods and properties is to use actual factory objects. If you want to read more about such objects, and other ways that I usually use the factory pattern, check out "Using the factory pattern to avoid shared state in Swift", "Dependency injection using factories in Swift" and "Using lazy properties in Swift".

What do you think? Do you currently use static factory APIs for setup, or is it something you'll try out? Let me know - along with any questions, comments or feedback you might have - on Twitter @johnsundell.

Thanks for reading! 🚀