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! 🚀