In Swift you’ll be constructing objects all the time. It might seem simple at first glance but initializers in Swift are a pretty deep topic. In this article we’ll cover designated and convenience initializers. We’ll touch a bit on the failable initializers, required initializers and initializers in structs. We’ll finish with a deinit 🙂
Initialization
Initialization is a process of preparing your class (or struct) for use. Swift is a bit strict on initialization. All your properties have to be initialized in order for you to use the class (there are some exceptions to this, we’ll cover them later).
When you define your class and your variables, you can assign default values to your variables, if that makes sense to you. Or you can use the initializer to set the values of your properties. For example, you could set default values for your properties like this:
class Movie { var title: String = "No Title" var year: Int = 1999 } let unknownMovie = Movie() print(unknownMovie.title) // prints: No Title
If you provide default values for all the non-optional properties you will get this default initializer for free. Another way to initialize your properties is to create your own initializer:
class Movie { var title: String var year: Int init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } } let knownMovie = Movie(withTitle: "Be Creative", andYear: 1991) print(knownMovie.title) // prints: Be Creative
Optional Properties
The properties you don’t have to initialize are optional properties. It kinda makes sense when you think about it, because they can be nil anyway. If you added an optional property to our example above, your code would compile just fine:
class Movie { var title: String var year: Int var story: String? init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } }
The same applies to implicitly unwrapped optional properties. If we were to make the ‘story’ property of ‘String!’ type the code would still compile.
Constant Properties
You can set your constant properties in the initializers, as you would expect:
class Movie { let title: String var year: Int var story: String! init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } }
You can set them only once, and a subclass can’t set the constant properties of its superclass.
Just a note on setting the properties. If you’re setting your properties directly or in an initializer your property observers won’t get called.
Designated Initializers and Convenience Initializers
If you don’t provide all the default values for your properties you must have an initializer or your code won’t compile. In swift we have multiple types of initializers. Designated initializer is an initializer that initializes all the non-optional properties. You must have at least one designated initializer. From the example above, we created a designated initializer:
class Movie { let title: String var year: Int var story: String? init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } }
Another type of initializer is a convenience initializer. You create a convenience initializer by adding a keyword ‘convenience’ in front of it. Convenience initializer must call a designated initializer. Or it can call another convenience initializer, as long as the last convenience initializer in the chain calls the designated initializer. Let’s see an example of a convenience initializer:
class Movie { let title: String var year: Int var story: String! init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } convenience init(withTitle title: String) { self.init(withTitle: title, andYear: 2017) } } let aMovie = Movie(withTitle: "Be Creative") print("\(aMovie.title) : \(aMovie.year)") // prints: Be Creative : 2017
Inheritance and Initializers
For classes, initializers get a bit more interesting. Convenience initializers can only call other initializers within the same class, they can’t call the superclass initializers. Only designated initializers can call the superclass initializers. There’s a great graphic from Apple that illustrates this:
Subclasses don’t inherit a superclasses’ initializers. So you will have to provide overrides yourself. There are two cases where your subclass will automatically inherit the superclass initializers: If you provide all the default values for the properties, or if you implement all of the superclasses designated initializers. For example:
class Movie { let title: String var year: Int var story: String? init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } convenience init(withTitle title: String) { self.init(withTitle: title, andYear: 2017) } } class DigitalMovie: Movie { var medium: String override init(withTitle title: String, andYear year: Int) { self.medium = "Digital" super.init(withTitle: title, andYear: year) } } let aMovie = DigitalMovie(withTitle: "Be Creative") print("\(aMovie.title) : \(aMovie.medium)") // prints: Be Creative : Digital
Here we added a new property, we don’t have a default value, and we’re overriding a designated initializer of our superclass. We can see that we can use the superclass convenience initializer when creating an instance of the ‘DigitalMovie’ class.
Required Initializers
If you want every subclass to implement a certain initializer, just mark it with the ‘required’ keyword. For example:
class Movie { let title: String var year: Int var story: String? required init(withYear year: Int) { self.title = "No Title" self.year = year } init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } convenience init(withTitle title: String) { self.init(withTitle: title, andYear: 2017) } } class DigitalMovie: Movie { var medium: String required init(withYear year: Int) { self.medium = "Digital" super.init(withYear: year) } override init(withTitle title: String, andYear year: Int) { self.medium = "Digital" super.init(withTitle: title, andYear: year) } }
We have a required initializer in the ‘Movie’ class and we had to implement it in our ‘DigitalMovie’ class as well.
Failable Initializers
When you’re creating your objects, maybe you don’t have all the data that you need, or some of it is invalid. What ever your case is, you have an option to abort object initialization. You can easily do this with a failable initializer. Failable initializers have a question mark after the init keyword, you must have seen it all around the place. You can fail at any time during the initialization by returning nil. Technically speaking, swift initializers don’t return a value (unlike objective-c). Let’s see a quick example of a failable initializer:
class Movie { let title: String var year: Int var story: String? init?(withTitle title: String, andYear year: Int) { if year < 1900 { return nil } self.title = title self.year = year } } let aMovie = Movie(withTitle: "Be Creative", andYear: 1800) print("\(aMovie?.title) : \(aMovie?.year)") // prints: nil : nil
We’re checking for a year here, and if it’s less than 1900 we fail the initialization. Notice how ‘aMovie’ property is now an optional type.
This initializer will create an optional instance of a ‘Movie’ type. You could also define your initializer to return an implicitly unwrapped instance of a ‘Movie’ type by using the exclamation mark operator:
class Movie { let title: String var year: Int var story: String? init!(withTitle title: String, andYear year: Int) { if year < 1900 { return nil } self.title = title self.year = year } } let aMovie: Movie = Movie(withTitle: "Be Creative", andYear: 1800) // fatal error print("\(aMovie.title) : \(aMovie.year)")
Throwable Initializers
Another type of a failable initializer is a throwable initializer. Perhaps for your use case it would make more sense to throw an error. Especially if you want to provide a bit more details about why the initialization failed. We can modify our example a bit so it would throw an error:
enum MovieError: Error { case InvalidYear case TitleTooShort } class Movie { let title: String var year: Int var story: String? init(withTitle title: String, andYear year: Int) throws { if year < 1900 { throw MovieError.InvalidYear } if title.characters.count < 3 { throw MovieError.TitleTooShort } self.title = title self.year = year } } do { let aMovie: Movie = try Movie(withTitle: "Be Creative", andYear: 1800) print("\(aMovie.title) : \(aMovie.year)") } catch { print("Failed to create a movie: \(error)") } // prints: Failed to create a movie: InvalidYear
I won’t go into details about error handling in swift, because it’s a topic on its own. You can see from the example that you’re getting a lot more details now when your initialization fails.
Memberwise Initializers in Structs
These initializers are specific for structures. If you don’t define your custom initializers in structs you get a free initializer that will take in all the properties of the struct as parameters and initialize it. Let’s change our example a bit to see this in action:
struct Movie { let title: String var year: Int var story: String? } let aMovie = Movie(title: "Be Creative", year: 1999, story: nil) print("\(aMovie.title) : \(aMovie.year)") // prints: Be Creative : 1999
You will get this free initializer even if you provide default values for your properties, unless one of your properties is a constant, then you get nothing 🙂
Deinitializers
What better way to end a post than with a deinit 🙂 Deinitializers are quite simple. They are methods that take no parameters and will be called just before your class is deallocated, they are available only on class types. You can use them to perform your final cleanups before your class is destroyed. For example, you can remove yourself as an observer if you registered for notifications. You can’t call deinit directly, and your superclass deinit is called just before your own deinit implementation returns. Let’s see this on a quick example:
class Movie { let title: String var year: Int var story: String? init(withTitle title: String, andYear year: Int) { self.title = title self.year = year } convenience init(withTitle title: String) { self.init(withTitle: title, andYear: 2017) } deinit { print("This is the end") } } class DigitalMovie: Movie { var medium: String override init(withTitle title: String, andYear year: Int) { self.medium = "Digital" super.init(withTitle: title, andYear: year) } deinit { print("Goodbye world") } } func makeAndBrakeAMovie() { let aMovie = DigitalMovie(withTitle: "Be Creative") print("\(aMovie.title) : \(aMovie.medium)") } makeAndBrakeAMovie() // prints // Be Creative : Digital // Goodbye world // This is the end
Here we can see that our deinit is being called first, and then the super deinit is being called automatically.
Conclusion
Object construction might seem like a simple topic, but it can be quite complex. There are quite a few rules to follow in swift, but once you understand them you’ll have no problems. In this article we covered the basics, and I hope I shed some light on object construction (and destruction) in swift.
Have a nice day 🙂
Dejan.
Could you provide a reason why a convenience initializer would be useful? Similarly, what’s the reason for a required initializer? Thanks!
Well, a class must have a required initializer, and a convenience initializer is useful when you want to initialize a class with some default parameters, for example.
It’s not true that a class must have a `required` initializer. A class must have at least one designated initializer – either a `default` one or one that you provide. That’s the only required thing.
A good example of when the use of ‘required’ initializers is needed is when a class implements a protocol that lists some initializers. In this case any initializers listed in the protocol must be ‘required’ – this is necessary to guarantee that any subclasses will still provide all the initializers needed to keep conforming to that protocol.
You’re absolutely right Antonio. I actually meant designated, I was in a hurry when replying to the comment. Thanks for the much better explanation 🙂
class Movie {
let title: String
var year: Int
var story: String?
init(withTitle title: String, andYear year: Int) {
self.title = title
self.year = year
}
convenience init(withTitle title: String) {
self.init(withTitle: title, andYear: 2017)
}
}
class DigitalMovie: Movie {
var medium: String
override init(withTitle title: String, andYear year: Int) {
self.medium = “Digital”
super.init(withTitle: title, andYear: year)
}
}
let aMovie = DigitalMovie(withTitle: “Be Creative”)
print(“\(aMovie.title) : \(aMovie.medium)”)
Here we added a new property, we don’t have a default value, and we’re overriding a designated initializer of our superclass. We can see that we can use the superclass designated initializer (Not convenience) when creating an instance of the ‘DigitalMovie’ class. So Need To correct blog here.