A Better Syntax for Configurable Initializations
Published on Jan 26, 2023Get a quick glimpse of how Tiny Currency simplifies currency conversion with up to date rates, multi-currency support, and easy-to-use widgets. Perfect for on-the-go use, our app ensures you're always prepared, no matter where your travels take you.
Note that this post has been republished from a previous blog, originally published on January 23, 2019
What is a configurable initialization?
First thing’s first, let’s define what I mean by configurable initialization.
Chances are you have tried to initialize a UIView
but you needed to set some parameters on it, which resulted in something like the following:
let view: UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
This is a pretty practical use case in iOS development, but there is a bit of redundancy here.
Let’s see if we can make it better
Initializer extensions
To make this syntax a little cleaner, could we get rid of the initialization line and the return line? That way, we are just left with a line setting the background color.
Let’s create a class that takes any class of type UIView
, and initializes it with some kind of configuration closure. We will start by initializing a new instance of the generic UIView
or subclass, configure it, then return it:
final class Initializer<T: UIView> {
static func initialize(configure: (T) -> Void) -> T {
let t = T()
configure(t)
return t
}
}
So far so good. This function does exactly what we did before, but it does it in a more generic way. This way, we can initialize a view like so:
class SomeView: UIView {
var something: Int = 0
}
let someView = Initializer<SomeView>.initialize {
$0.backgroundColor = .green
$0.something = 0
}
Not only do we now have a cleaner way to initialize views, but we can do so for any subclasses of UIView
also.
Let’s make it more generic
So we created a nice clean way of initializing UIView
and subclasses of UIView
but what about other classes? Also, I don’t know if I’m a fan of the new syntax we created. It forces developers to change the way they initialize objects, and it’s not very intuitive.
I think a cleaner solution would be to implement a custom initializer for all objects.
protocol InitConfigurable {
init()
}
extension InitConfigurable {
init(configure: (Self) -> Void) {
self.init()
configure(self)
}
}
Not only is this a cleaner solution, but it actually makes more sense too. Any object that conforms to the InitConfigurable
protocol must adhere to an init
function, but also gets a free configurable initializer as an extension. The extension init
calls the empty init
and then configures itself with the given closure. It can be used like so:
class SomeView: UIView, InitConfigurable {
var something: Int = 0
}
let someView = SomeView {
$0.backgroundColor = .green
$0.something = 1
}
But wait! There’s more!
Ok one last thing.
I really don’t want to have to adhere to InitConfigurable
for every object that I want to be able to configure.
Here’s a simple solution that enables InitConfigurable
for all NSObject
s. Have fun configuring!
extension NSObject: InitConfigurable { }