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

Namespacing Swift code with nested types

Published on 02 Apr 2017

While Swift does not yet feature a dedicated namespace keyword, it does support nesting types within others. Let’s take a look at how using such nested types can help us improve the structure of our code.

Many Swift developers are used to do namespacing through including structural levels in the actual name of a type — using names like PostTextFormatterOption (an Option for a Text Formatter used to format Posts). This is probably because this was pretty much the only way to do sort of a “poor man’s namespacing” in Objective-C & C, and like many other conventions it has carried over to Swift.

Let’s use the type mentioned above as an example, and take a look at the implementations of Post, PostTextFormatter & PostTextFormatterOption:

struct Post {
    let id: Int
    let author: User
    let title: String
    let text: String
}

class PostTextFormatter {
    private let options: Set<PostTextFormatterOption>

    init(options: Set<PostTextFormatterOption>) {
        self.options = options
    }

    func formatTitle(for post: Post) -> String {
        return post.title.formatted(withOptions: options)
    }

    func formatText(for post: Post) -> String {
        return post.text.formatted(withOptions: options)
    }
}

enum PostTextFormatterOption {
    case highlightNames
    case highlightLinks
}

Now let’s take a look at how the above types change if we instead structure them as nested types inside of Post:

struct Post {
    class TextFormatter {
        enum Option {
            case highlightNames
            case highlightLinks
        }

        private let options: Set<Option>

        init(options: Set<Option>) {
            self.options = options
        }

        func formatTitle(for post: Post) -> String {
            return post.title.formatted(withOptions: options)
        }

        func formatText(for post: Post) -> String {
            return post.text.formatted(withOptions: options)
        }
    }

    let id: Int
    let author: User
    let title: String
    let text: String
}

One big advantage of the nested types approach is that we can now clearly see the structure and relationship between our types just by taking a quick look at our code. We have also reduced the verbosity in our initializer, making it shorter and easier to read (the options argument is now simply of type Set<Option> instead of Set<PostTextFormatterOption>).

We now also get a clear sense of hierarchy at the call site — everything related to a Post is now neatly structured under the Post. namespace. Here’s what formatting a post’s text looks like:

let formatter = Post.TextFormatter(options: [.highlightLinks])
let text = formatter.formatText(for: post)

However, using nested types like above also has a pretty significant downside. The code is kind of “backwards” vertically, where the actual content of the parent type gets pushed all the way down to the bottom. So let’s try to fix that by flipping our structure — moving our nested types down to the bottom, instead of having them at the top (we also throw in some MARKs, for good measure).

struct Post {
    let id: Int
    let author: User
    let title: String
    let text: String

    // MARK: - TextFormatter
    
    class TextFormatter {
        private let options: Set<Option>

        init(options: Set<Option>) {
            self.options = options
        }

        func formatTitle(for post: Post) -> String {
            return post.title.formatted(withOptions: options)
        }

        func formatText(for post: Post) -> String {
            return post.text.formatted(withOptions: options)
        }

        // MARK: - Option
        
        enum Option {
            case highlightNames
            case highlightLinks
        }
    }
}

Whether you prefer the nested types to be on the top or bottom is definetly going to be a personal preference. I kind of like how it lets me keep the actual content of the parent type at the top — while still giving the code the hierarchical benefits of nested types.

However, like with many things in Swift, it turns out there’s a couple of more ways to implement namespacing & nested types.

Nested types in extensions

One option is to use extensions to implement your nested types. This gives you a clearer separation between the types, while still retaining the hierarchy both in the implementation and at the call site.

Here’s what that looks like for our types:

struct Post {
    let id: Int
    let author: User
    let title: String
    let text: String
}

extension Post {
    class TextFormatter {
        private let options: Set<Option>

        init(options: Set<Option>) {
            self.options = options
        }

        func formatTitle(for post: Post) -> String {
            return post.title.formatted(withOptions: options)
        }

        func formatText(for post: Post) -> String {
            return post.text.formatted(withOptions: options)
        }
    }
}

extension Post.TextFormatter {
    enum Option {
        case highlightNames
        case highlightLinks
    }
}

Using typealiases

You can also add typealiases to the original code (that didn’t use nested types) to achieve a nested type-ish behavior. While this doesn’t give you the same level of hierarchy in the implementation, it does help reduce the verbosity — and it also provides the same benefits at the call site as when using nested types.

Here’s what that option looks like:

struct Post {
    typealias TextFormatter = PostTextFormatter

    let id: Int
    let author: User
    let title: String
    let text: String
}

class PostTextFormatter {
    typealias Option = PostTextFormatterOption

    private let options: Set<Option>

    init(options: Set<Option>) {
        self.options = options
    }

    func formatTitle(for post: Post) -> String {
        return post.title.formatted(withOptions: options)
    }

    func formatText(for post: Post) -> String {
        return post.text.formatted(withOptions: options)
    }
}

enum PostTextFormatterOption {
    case highlightNames
    case highlightLinks
}

Conclusion

Using nested types can help you create a really nice structure & hierarchies in your code, to help make it clearer what the relationships are between your various types — both in the implementation and at the call site.

However, depending on what technique you choose to implement them, you may face other challenges and side effects — so I think picking the technique depending on the situation becomes really important, in order to end up with a net win.

What do you think? Which one of the above techniques to you prefer for namespacing your code? Or have you found another one? Let me know, along with any questions or comments, on Twitter @johnsundell.

(Oh, and by the way, as of Swift 3.1 nested types can also be used inside generics! 🎉 …but more on that in an upcoming post 😉)

Thanks for reading! 🚀