Types need to have a unique name. In Objective-C, naming collisions with other libraries and frameworks are avoided by adding a prefix to a type. That is why we use UIView
instead of View
and CGRect
instead of Rect
.
This isn't necessary in Swift thanks to modules. In Swift, namespacing is implicit. Types are implicitly scoped by the module they are defined in. Take a look at the following example.
let number: Swift.Int = 1
Int
is defined in the Swift standard library. The example shows that Int
can be accessed through a module with name Swift
.
let number: Int = 1
This example shows that there is no need to explicitly include the name of the module in the type declaration. This isn't true if we decide to define a struct with name Int
. The following example is valid, but I hope you agree that it is confusing.
import Foundation
struct Int {
let number: Swift.Int
}
let number1: Swift.Int = 1
let number2: Int = Int(number: number1)
We define a struct with name Int
. The struct defines a property with name number
of type Swift.Int
. number1
is of type Swift.Int
while number2
is of type Int
. We need to write Swift.Int
to avoid a naming collision with Int
. This is a scenario that you don't run into very often if you choose the names of your types carefully.
Even though modules are an important step forward, they aren't as flexible as many developers would want them to be. Swift currently doesn't offer a solution to namespace types and constants within modules.
Structs and Namespaces
Even though Swift doesn't support namespaces within modules, there are several viable solutions to this problem. The first solution uses a struct to create a namespace and it looks something like this.
import Foundation
struct API {
static let baseURL = "https://example.com/v1/"
static let token = "sdfiug8186qf68qsdf18389qsh4niuy1"
}
We define a struct, API
, and declare two static, constant properties. I believe this solution was first coined by Jesse Squires several years ago. It works great, it is easy to adopt, and the syntax to access the constants is intuitive and easy to read.
import Foundation
if let url = URL(string: API.baseURL) {
...
}
There is one unwanted side effect, though. The API
struct can be instantiated. While this isn't a problem in itself, it may confuse other developers working on the project. You could declare the initializer of the API
struct privately, making it inaccessible to other parts of the project.
Using a struct to host a number of static members isn't the best solution and there is even a SwiftLint rule, Convenience Type, that guards against this. There is a better solution that doesn't require a workaround.
Enums and Namespaces
The only downside of using structs is that a struct can be instantiated unless the initializer is declared privately. We can avoid this unwanted side effect by using an enum without cases. I have been using this solution for years and it works like a charm. Let's update the previous example.
import Foundation
enum API {
static let baseURL = "https://example.com/v1/"
static let token = "sdfiug8186qf68qsdf18389qsh4niuy1"
}
The API
enum cannot be instantiated because it defines no cases. This solution is cleaner and requires less code. It is exactly what we need.
Grouping Types
Another technique I use often in larger projects makes clever use of enums and extensions. Say we are adding the ability to add videos to a watch list. We start by creating a namespace for the feature. This is as simple as defining an enum with name WatchList
.
enum WatchList {}
We can namespace the types associated with the feature by extending the WatchList
enum.
import UIKit
extension WatchList {
final class ListViewController: UIViewController {
...
}
}
We don't need to worry about naming collisions and the name of the type, WatchList.ListViewController
, helps document the type. You can apply this technique in any project.
let listViewController = WatchList.ListViewController()
Nesting Types
In recent years, Apple started applying a similar technique to group related types. Types that are closely related to another type are defined as nested types. Take UITableViewCell.EditingStyle
as an example. EditingStyle
is a nested type of UITableViewCell
. This is convenient and avoids long names.
UITableViewCell.EditingStyle
Apple applies a similar technique to namespace constants that are closely related to a type. Notification names are a common example. Notification names used to have long names to add context and meaning. That is no longer true. The application life cycle notification names are constant type properties of UIApplication
. didEnterBackgroundNotification
is a constant, class property of UIApplication
. I like this pattern a lot.
UIApplication.didEnterBackgroundNotification
Naming Collisions
In Swift, types can have the same name. Earlier in this episode, we defined a struct with name Int
even though the Swift standard library also defines a struct with that name. This is acceptable as long as you avoid any ambiguity. Take a look at this example.
enum Error {
// MARK: - Cases
case notFound
case badRequest
case noConnection
}
let error: Error
We define an enum with name Error
. As you know, the Swift standard library defines a protocol with that name. In this example, the error
constant is of type Error
, not Swift.Error
.
To avoid confusion, it is fine to prefix the type with the module it is defined in. In this example, the error
constant is of type Swift.Error
.
let error: Swift.Error
While it is recommended to avoid names that can cause confusion or ambiguity, I ignore this recommendation from time to time. Take a look at this example. We define a class with name APIClient
. The class defines an enum, APIClientError
, that conforms to the Error
protocol.
class APIClient {
enum APIClientError: Error {
// MARK: - Cases
case notFound
case badRequest
case noConnection
}
}
Naming the nested enum Error
triggers a compiler error because it conflicts with Swift's Error
protocol.
It is possible to name the enum Error
if we are explicit about the definition of the protocol it conforms to. We write Swift.Error
instead of Error
.
class APIClient {
enum Error: Swift.Error {
// MARK: - Cases
case notFound
case badRequest
case noConnection
}
}
You may feel that I am overcomplicating the implementation, but I don't agree. I prefer a type with name APIClient.Error
over a type with name APIClient.APIClientError
. I only ever use this technique for the Error
protocol, but I like it a lot as it cleans up the API.
What's Next?
Even though Swift doesn't support namespaces within modules, enums solve the problem elegantly with very little overhead. I strongly recommend using enums over structs to avoid any confusion. Namespaces keep your code clean, organized, and readable.