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

Using static protocol APIs to create conforming instances

Published on 20 Aug 2021
Basics article available: Protocols

New in Swift 5.5: It’s now possible to define protocol APIs that let us use Swift’s very convenient “dot syntax” to create conforming instances, which in turn can make certain protocols act more like enums, while still retaining all of the flexibility that protocols give us.

For example, when applying a style to a SwiftUI List, we currently (in Swift 5.4) have to spell out the whole name of that style’s type — like this:

struct ItemList: View {
    var items: [Item]

    var body: some View {
        List(items) { item in
            ...
        }
        .listStyle(InsetGroupedListStyle())
    }
}

But now, we’re instead able to refer to the above style simply by typing .insetGrouped, which should feel a lot more intuitive to many Swift developers, as that’s the same type of syntax that we commonly use with other kinds of configuration APIs:

struct ItemList: View {
    var items: [Item]

    var body: some View {
        List(items) { item in
            ...
        }
        .listStyle(.insetGrouped)
    }
}

The good news is that the above new API style is even fully backward compatible with earlier versions of Apple’s operating systems (as long as we’re using Xcode 13 to build our app).

So how can we make use of this new feature within our own code as well? As an example, let’s say that an app that we’re working on contains the following ImageLoader API, which is modeled as a protocol that then has separate implementations for loading either internal or external images:

protocol ImageLoader {
    func loadImage(from url: URL) async throws -> Image
}

class ExternalImageLoader: ImageLoader {
    ...

    func loadImage(from url: URL) async throws -> Image {
        // Load the image from an external server.
        ...
    }
}

class AuthenticatedImageLoader: ImageLoader {
    private let token: AccessToken
    ...

    func loadImage(from url: URL) async throws -> Image {
        // Load the image from our app's own server, using
        // the loader's access token.
        ...
    }
}

To make it possible to refer to the above two ImageLoader implementations using dot syntax, all that we have to do is to define a type-constrained extension for each one — which in turn contains a static API for creating an instance of that type:

extension ImageLoader where Self == ExternalImageLoader {
    static var external: Self { Self() }
}

extension ImageLoader where Self == AuthenticatedImageLoader {
    static func authenticated(with token: AccessToken) -> Self {
        Self(token: token)
    }
}

With the above in place, we can now easily refer to either .external or .authenticated, depending on what type of image loader that we’re looking to create:

let searchListVC = ItemListViewController(
    dataSource: makeSearchListDataSource(),
    imageLoader: .external
)

let friendsListVC = ItemListViewController(
    dataSource: makeFriendsListDataSource(),
    imageLoader: .authenticated(with: user.accessToken)
)

While the above technique does require a bit more code than when using an enum (or a struct with static APIs), it does let us retain the main benefits of using protocols — such as being able to implement separate functionality using separate types — while still making our call sites much simpler.

Personally, I think that this new language feature will be especially useful when working with protocols that have a somewhat finite set of implementations — such as SwiftUI’s various styling protocols, and the above ImageLoader example.

If you have any questions, comments, or feedback, then feel free to reach out via email.

Thanks for reading!