Ruby allows developers to easily make new namespaces using the module
keyword. For example, the ActiveRecord
namespace contains a class called Base
, like so:
module ActiveRecord
class Base
end
end
Inside the ActiveRecord
module, you can refer to this class as just Base
, whereas outside the module, you’d refer to it as ActiveRecord::Base
. You can add multiple classes, functions, and variables to any Ruby module
.
(For those that are curious, ActiveRecord::Base
is ActiveRecord’s equivalent to NSManagedObject
. It’s the class you subclass from to make new ORM objects.)
I want this effect in Swift. Swift has something called modules, but those are really just frameworks. I know they can give you some potential compilation speedups, but making a Swift module is pretty high ceremony and involves a lot of configuration. I just want to type some code, and isolate some classes from some other classes.
This great Natasha the Robot post from a few months ago finally gave me the tools I needed to make Swift namespaces a reality.
She used an enum with no cases (because enums with no cases crucially can’t be initialized) and added static properties to the enum to give them a fake namespace.
enum ColorPalette {
static let Red = UIColor(red: 1.0, green: 0.1491, blue: 0.0, alpha: 1.0)
static let Green = UIColor(red: 0.0, green: 0.5628, blue: 0.3188, alpha: 1.0)
static let Blue = UIColor(red: 0.0, green: 0.3285, blue: 0.5749, alpha: 1.0)
}
I want to take advantage of this syntax because I find that the features I write have a bunch of classes that all have the same prefix. For example, the authentication flow in an app might have lots of little components: AuthenticationCoordinator
, AuthenticationData
, AuthenticationFormView
, AuthenticationFormConfiguration
, AuthenticationFormField
, and AuthenticationFormFieldConfiguration
, as well other namespaced objects like SignupViewController
and SignupView
.
Some of these class names are starting to get pretty long! Since something just called FormField
would pollute the global namespace, I have to tack on the “Authentication” prefix. I don’t want it to collide with any other form fields I might have in my app. If we wanted to take Natasha’s scoping pattern to the next level, we could make an Authentication
enum.
enum Authentication { }
From there, we can extend it to add nested types:
extension Authentication {
struct Data {
let username: String
let password: String
//etc
}
class Coordinator {
//implementation
}
}
Inside the Coordinator
class, we can just refer to Authentication.Data
as simply Data
(since they’re both inside the Authentication
module). Outside the module, we’ll refer to it as Authentication.Data
. This is exactly the behavior we want.
By adding one character of code (the period) for references outside the module, we get to drop a ton of characters when working with references inside the module. Further, having to add the prefix and the period explicitly will formalize all class names inside of the module. It’ll be super obvious when we’re trying to use these types from outside the module.
We can add more extensions across multiple files.
extension Authentication {
class LoginViewController {
//implementation
}
}
A good way to use this pattern would be namespacing a view controller and its smarter view together.
enum Signup { }
//SignupViewController.swift
extension Signup {
class ViewController {
func loadView() {
self.view = View()
}
//etc
}
}
//SignupView.swift
extension Signup {
class View {
//etc
}
}
You can also nest these modules, like Natasha shows in her post.
extension Authentication {
enum Form {
class View {
}
}
}
When nesting, if you want to move things to a different file, you’ll have to use a slightly different syntax:
// create the new triply-nested namespace
extension Authentication.Form {
enum Field { }
}
// in a different file
extension Authentication.Form.Field {
struct Data {
}
class View {
}
}
Because Swift doesn’t allow you to use the extension
keyword outside of the file scope, when nesting, you have to use the dot-syntax to access your nested namespace.
There’s one big downside to abusing an enum
type like this. The protocol
keyword, like the extension
keyword, is limited to the file scope only, so you can’t declare any protocols inside your namespace.
It’s a bit of a heretical approach to structuring code, but many new ideas seem heretical at first. Ideally, Swift would add a module
keyword. But barring that, they could make protocols work in enums and I’d be happy to use the enum
trick to fake namespaces.