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.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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!

By NASA and The Hubble Heritage Team (STScI/AURA) – Source

By NASA and The Hubble Heritage Team (STScI/AURA)

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.