今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

Nested Naming Convention in Swift Code

Nested Naming Conventions in Swift Code#

Swift supports nested naming conventions with other types, although it does not have dedicated naming keywords. Let's take a look at how we can use nested types to optimize the structure of our code.

Most Swift developers are accustomed to naming types based on their actual names in the structure. For example: PostTextFormatterOption (an Option for formatting Posts of type TextFormatter). This may be because we have brought the terrible naming habits we developed in Objective-C & C into Swift.

Let's take the types mentioned above as an example and see the implementation of Post, PostTextFormatter, and PostTextFormatterOption:

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

class PostTextFormatter {
    private let options: Set
    
    init(options: Set) {
        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 see what happens if we nest the above types within Post:

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

        private let options: Set

        init(options: Set) {
            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 great advantage of nested types is that we can quickly see the structure and relationships between types at a glance. We also reduce the length of initialization code, making it shorter and easier to read (the options parameter type changes from Set< PostTextFormatterOption > to Set< Option >).

The calling hierarchy is also more concise and clear - everything related to Post becomes Post.namespace. Formatting the text of a post looks like this:

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

However, using nested types also has a significant drawback. The code looks "upside down" because the actual content of the parent type is pushed to the bottom. Let's try to fix this problem by moving the code of the nested type from above to below (and add some MARKs for clarity).

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

    // MARK: - TextFormatter
    class TextFormatter {
        private let options: Set

        init(options: Set) {
            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 to place nested types above or below is purely a personal preference. I prefer to have the content of the parent type at the top - and at the same time enjoy the convenience of nested types.

In fact, there are several other ways to implement naming and nested types in Swift.

Implementing Nested Types Using Extensions#

Another option for implementing nested types is using extensions. This method maintains the hierarchy relationship during implementation and calling, while clearly separating each type.

It looks like this:

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

extension Post {
    class TextFormatter {
        private let options: Set

        init(options: Set) {
            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#

Typealiases can also be used to achieve similar nested type code in the original code (although it does not actually use nested types). Although this method does not have a nested hierarchy in implementation, it reduces the length of the code - and the calling looks the same as using nested types.

The code is as follows:

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

    init(options: Set) {
        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 helps write elegant and structured code, making the relationships between multiple types clearer - both in implementation and calling.

However, due to different implementation methods, different challenges and side effects may be encountered - so I think it is important to choose the corresponding implementation based on the actual situation, in order to achieve beauty.

What do you think? Which of the above techniques do you prefer? Or do you have other methods? Tell me your questions and opinions on Twitter@johnsundell.

Thanks for reading! 🚀

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.