As developers, we use initializers all the time to create instances of various types. We also create our own initializers when implementing custom classes or structs. But should initializers always return an instantiated object of the type they are implemented into?
It makes sense sometimes not to return an object if the initialization of one or more properties that play crucial role in the type does not succeed. A failable initializer in Swift is an initializer that allows to do that, as it can potentially return nil if one or more conditions are not satisfied.
A hands-on example
Let’s see an example to demonstrate that. Suppose that we are implementing a custom class that applies various filters to images. In order to do so, we need an image property first:
1 2 3 4 5 |
class FilteredImage { var image: UIImage? } |
For flexibility reasons, let’s also suppose that we can initialize an instance of the above class in two ways; either by providing an actual image object, or an image name. For the former case, such an initializer is pretty simple:
1 2 3 4 5 6 7 8 9 |
class FilteredImage { var image: UIImage? init(with image: UIImage) { self.image = image } } |
In case of providing the image name only, things need some additional consideration. We initialze an image object using just the name like so:
1 2 3 |
let image = UIImage(named: imageName) |
However, if there is no image with the specified name in the assets catalog, the above will return nil and the image
constant will also be nil. In fact, the UIImage(named:)
method is a failable initializer of the UIImage class!
We will use the above to write our own failable initializer based on the following thinking; if UIImage(named:)
returns an actual image, then we will assign it to the image
property of the class. If not, then we’ll return nil from our own initializer as well, because we have no image to apply filters on.
There are two requirements in order to write a failable initializer:
- The question mark (?) symbol must suffix the
init
keyword, before the parentheses, indicating that the initializer returns an optional value. - Initializer must contain at least one path of code execution that returns nil.
Here’s the implementation of the custom failable initializer for this example:
1 2 3 4 5 6 7 8 9 10 11 12 |
class FilteredImage { … init?(with imageName: String) { guard let image = UIImage(named: imageName) else { return nil } self.image = image } } |
We can then create an instance of FilteredImage
normally, as we would do with any other initializer:
1 2 3 |
let filteredImage = FilteredImage(with: “bird”) |
Note that filteredImage
is an optional value. To use it without carrying over all limitations that optionals introduce, we should unwrap it first using an if-let
or a guard
statement:
1 2 3 4 5 6 7 8 |
guard let filteredImage = FilteredImage(with: “bird”) else { // filteredImage is nil return } // filteredImage is initialized and we use it normally |
There is one last important thing to remember; nil is the only allowed value to return from a failable initializer. In case where the initialization does not fail, then there is nothing to return; just handle everything as you would do in a normal initializer.
Summary
That’s the failable initializer in Swift; a handy tool in order to return nil objects in case the desired initialization fails. Have you been using it already? Are you planning to do so? No matter what, I hope there is something new for you in this post that will improve your daily development workflow. Thank you for reading!
You can find this post published on Medium too.