Swift Tutorial: Initialization In Depth, Part 1/2
Take your Swift skills to the next level by learning about how your instances are initialized in this in-depth two-part tutorial on initialization! By René Cacheaux.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Swift Tutorial: Initialization In Depth, Part 1/2
30 mins
- Getting Started
- Banking on the Default Initializer
- Banking on the Memberwise Initializer
- Implementing a Custom Initializer
- Avoiding Duplication with Initializer Delegation
- Introducing Two-Phase Initialization
- Putting Two-Phase Initialization to Work
- What if Things Go Wrong?
- Using Failable Initializers
- Throwing From an Initializer
- To Fail or to Throw?
- Where To Go From Here?
Implementing a Custom Initializer
Weather plays a key role in launching rockets, so you’ll need to address that in the data model. Declare a new struct named Weather
as follows:
struct Weather {
let temperatureCelsius: Double
let windSpeedKilometersPerHour: Double
}
The struct
has stored properties for temperature in degrees Celsius and wind speed in kilometers per hour.
Implement a custom initializer for Weather
that takes in temperature in degrees Fahrenheit and wind speed in miles per hour. Add this code below the stored properties:
init(temperatureFahrenheit: Double, windSpeedMilesPerHour: Double) {
self.temperatureCelsius = (temperatureFahrenheit - 32) / 1.8
self.windSpeedKilometersPerHour = windSpeedMilesPerHour * 1.609344
}
Defining a custom initializer is very similar to defining a method, because an initializer’s argument list behaves exactly the same as a method’s. For example, you can define a default argument value for any of the initializer parameters.
Change the definition of the initializer to:
init(temperatureFahrenheit: Double = 72, windSpeedMilesPerHour: Double = 5) {
...
Now if you call the initializer with no parameters, you’ll get some sensible defaults. At the end of your playground file, create an instance of Weather
and check its values:
let currentWeather = Weather()
currentWeather.temperatureCelsius
currentWeather.windSpeedKilometersPerHour
Cool, right? The default initializer uses the default values provided by the custom initializer. The implementation of the custom initializer converts the values into metric system equivalents and stores the values. When you check the values of the stored properties in the playground sidebar, you’ll get the correct values in degrees Celsius (22.2222) and kilometers per hour (8.047).
An initializer must assign a value to every single stored property that does not have a default value, or else you’ll get a compiler error. Remember that optional variables automatically have a default value of nil
.
Next, change currentWeather
to use your custom initializer with new values:
let currentWeather = Weather(temperatureFahrenheit: 87, windSpeedMilesPerHour: 2)
As you can see, custom values work just as well in the initializer as default values. The playground sidebar should now show 30.556 degrees and 3.219 km/h.
That’s how you implement and call a custom initializer. Your weather struct is ready to contribute to your mission to launch humans to Mars. Good work!
Avoiding Duplication with Initializer Delegation
It’s time to think about rocket guidance. Rockets need fancy guidance systems to keep them flying perfectly straight. Declare a new structure named GuidanceSensorStatus
with the following code:
struct GuidanceSensorStatus {
var currentZAngularVelocityRadiansPerMinute: Double
let initialZAngularVelocityRadiansPerMinute: Double
var needsCorrection: Bool
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Bool) {
let radiansPerMinute = zAngularVelocityDegreesPerMinute * 0.01745329251994
self.currentZAngularVelocityRadiansPerMinute = radiansPerMinute
self.initialZAngularVelocityRadiansPerMinute = radiansPerMinute
self.needsCorrection = needsCorrection
}
}
This struct holds the rocket’s current and initial angular velocity for the z-axis (how much it’s spinning). The struct also keeps track of whether or not the rocket needs a correction to stay on its target trajectory.
The custom initializer holds important business logic: how to convert degrees per minute to radians per minute. The initializer also sets the initial value of the angular velocity to keep for reference.
You’re happily coding away when the guidance engineers show up. They tell you that a new version of the rocket will give you an Int
for needsCorrection
instead of a Bool
. The engineers say a positive integer should be interpreted as true
, while zero and negative should be interpreted as false
. Your team is not ready to change the rest of the code yet, since this change is part of a future feature. So how can you accommodate the guidance engineers while still keeping your structure definition intact?
No sweat — add the following custom initializer below the first initializer:
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Int) {
let radiansPerMinute = zAngularVelocityDegreesPerMinute * 0.01745329251994
self.currentZAngularVelocityRadiansPerMinute = radiansPerMinute
self.initialZAngularVelocityRadiansPerMinute = radiansPerMinute
self.needsCorrection = (needsCorrection > 0)
}
This new initializer takes an Int
instead of a Bool
as the final parameter. However, the needsCorrection
stored property is still a Bool
, and you set correctly according to their rules.
After you write this code though, something inside tells you there must be a better way. There’s so much repetition of the rest of the initializer code! And if there’s a bug in the calculation of the degrees to radians conversion, you’ll have to fix it in multiple places — an avoidable mistake. This is where initializer delegation comes in handy.
Replace the initializer you just wrote with the following:
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Int) {
self.init(zAngularVelocityDegreesPerMinute: zAngularVelocityDegreesPerMinute,
needsCorrection: (needsCorrection > 0))
}
This initializer is a delegating initializer and, exactly as it sounds, it delegates initialization to another initializer. To delegate, just call any other initializer on self
.
Delegate initialization is useful when you want to provide an alternate initializer argument list but you don’t want to repeat logic that is in your custom initializer. Also, using delegating initializers helps reduce the amount of code you have to write.
To test the initializer, instantiate a variable named guidanceStatus
:
let guidanceStatus = GuidanceSensorStatus(zAngularVelocityDegreesPerMinute: 2.2, needsCorrection: 0)
guidanceStatus.currentZAngularVelocityRadiansPerMinute // 0.038
guidanceStatus.needsCorrection // false
The playground should compile and run, and the two values you checked for the guidanceStatus
properties will be in the sidebar.
One more thing — you’ve been asked to provide another initializer that defaults needsCorrection
to false. That should be as easy as creating a new delegating initializer and setting the needsCorrection
property inside before delegating initialization. Try adding the following initializer to the struct, and note that it won’t compile.
init(zAngularVelocityDegreesPerMinute: Double) {
self.needsCorrection = false
self.init(zAngularVelocityDegreesPerMinute: zAngularVelocityDegreesPerMinute,
needsCorrection: self.needsCorrection)
}
Compilation fails because delegating initializers cannot actually initialize any properties. There’s a good reason for this: the initializer you are delegating to could very well override the value you’ve set, and that’s not safe. The only thing a delegating initializer can do is manipulate values that are passed into another initializer.
Knowing that, remove the new initializer and give the needsCorrection
argument of the main initiaziler a default value of false
:
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Bool = false) {
Update guidanceStatus
‘s initialization by removing the needsCorrection
argument:
let guidanceStatus = GuidanceSensorStatus(zAngularVelocityDegreesPerMinute: 2.2)
guidanceStatus.currentZAngularVelocityRadiansPerMinute // 0.038
guidanceStatus.needsCorrection // false
👍 Way to go! Now you can put those DRY (Don’t Repeat Yourself) principles into practice.