Learning about how your instances are initialized in this tutorial.
Struct Initializations
let numberOfStageReuseLandingLegs: Int?
Compiler error
You won’t run into this often, since constant optionals are rarely needed. To fix the compiler error, assign a default value of nil
let numberOfStageReuseLandingLegs: Int? = nil
With this setup, numberOfStageReuseLandingLegs will never have a non-nil value. You cannot change it after initialization, since it is declared as a constant.
Swift structures (and only structures) automatically generate a memberwise initializer. This means you get a ready-made initializer for all the stored properties that don’t have default values. But when re-ordering structure properties, you might break instance initialization.
You only get a memberwise initializer if a structure does not define any initializers. As soon as you define an initializer, you lose the automatic memberwise initializer.
But what if you still need the automatic memberwise initializer? You can certainly write the equivalent initializer, but that’s a lot of work. Instead, move the custom initializer into an extension before you instantiate an instance.
Your struct will now be in two parts: the main definition, and an extension with your two-parameter initializer:
struct RocketStageConfiguration {
let propellantMass: Double
let liquidOxygenMass: Double
let nominalBurnTime: Int
}
extension RocketStageConfiguration {
init(propellantMass: Double, liquidOxygenMass: Double) {
self.propellantMass = propellantMass
self.liquidOxygenMass = liquidOxygenMass
self.nominalBurnTime = 180
}
}
If the main struct definition doesn’t include any initializers, Swift will still automatically generate the default memberwise initializer.
Initializer delegation
init(zAngularVelocityDegreesPerMinute: Double, needsCorrection: Int) {
self.init(zAngularVelocityDegreesPerMinute: zAngularVelocityDegreesPerMinute,
needsCorrection: (needsCorrection > 0))
}
What if Things Go Wrong?
There are two ways to handle initialization failures in Swift: using failable initializers, and throwing from an initializer. Initialization can fail for many reasons, including invalid input, a missing system resource such as a file, and possible network failures.
- Failable initializers can return nil to express an initialization failure.
init?(currentVolume: Double, currentLiquidType: String?) {
if currentVolume < 0 {
return nil
}
if currentVolume > 0 && currentLiquidType == nil {
return nil
}
self.currentVolume = currentVolume
self.currentLiquidType = currentLiquidType
}
As soon as an invalid input is detected, the failable initializer returns nil. You can return nil at any time within a structure’s failable initializer. This is not the case with a class’s failable initializer, as you’ll see in Part 2 of this tutorial.
- Failable initializers are great when returning nil is an option. For more serious errors, the other way to handle failure is throwing from an initializer.
struct Astronaut {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
enum InvalidAstronautDataError: Error {
case EmptyName
case InvalidAge
}
init(name: String, age: Int) throws {
if name.isEmpty {
throw InvalidAstronautDataError.EmptyName
}
if age < 18 || age > 70 {
throw InvalidAstronautDataError.InvalidAge
}
self.name = name
self.age = age
}
let johnny = try? Astronaut(name: "Johnny Cosmoseed", age: 17) //nil
When you call a throwing initializer, you write the try
keyword — or the try?
or try!
variations — to identify that it can throw an error. In this case, you use try? so the value returned in the error case is nil.
To Fail or to Throw?
Initializing using a throwing initializer and try? looks an awful lot like initializing with a failable initializer. So which should you use?
Consider using throwing initializers. Failable initializers can only express a binary failure/success situation. By using throwing initializers you can not only indicate failure, but also indicate a reason by throwing specific errors. Another benefit is that calling code can propagate any errors thrown by an initializer.
Failable initializers are much simpler though, since you don’t need to define an error type and you can avoid all those extra try? keywords.
Why does Swift even have failable initializers? Because the first version of Swift did not include throwing functions, so the language needed a way to manage initialization failures.
Class Initializations
Class initialization is very different from class initialization in Objective-C, and when writing class initializers in Swift for the first time, you’ll encounter many different compiler errors.
- see the initializer delegation equivalent for classes,
- learn the extra rules about failable and throwable initialization for classes,
- learn how classes delegate initialization up the class hierarchy, and
- learn how classes inherit initializers
Designated Initializers
A designated initializer
is just a fancy term for a non-delegating initializer. Just like with structures, these initializers are responsible for providing initial values to all non-optional stored properties declared without a default value.
Convenience Initializers
// Init #1b - Convenience
convenience init(model: String, serialNumber: String) {
self.init(model: model, serialNumber: serialNumber, reusable: false)
}
Notice how this looks just like a structure’s delegating initializer. The only difference here is that you have to prefix the declaration with the convenience keyword.
Similar to how they work with structs, convenience initializers let you have simpler initializers that just call through to a designated initializer.
Class initialization is a lot more complicated than struct initialization, because only classes support inheritance.
Swift does not initialize properties automatically to empty values; it only initializes optional typed properties automatically to nil. Programmers are responsible for defining initial values for all non-optional stored properties; otherwise the Swift compiler will complain.
class Tank: RocketComponent {
let encasingMaterial: String
}
Every subclass that introduces a new non-optional stored property without a default value needs at least one designated initializer.
You have three options to fix the compiler error:
- Add a designated initializer that calls or overrides the designated initializer for the superclass RocketComponent.
- Add a convenience initializer that calls the designated initializer for the superclass RocketComponent.
- Add a default value for the stored property.
In Swift, a subclass can only initialize properties it introduces. Subclasses cannot initialize properties introduced by superclasses. Because of this, a subclass designated initializer must delegate up to a superclass designated initializer to get all of the superclass properties initialized.
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.encasingMaterial = encasingMaterial
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
The rule for this is super easy: you can only set properties introduced by the subclass before delegation, and you can’t use the new instance until phase 2.
// Init #2a - Designated
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
self.encasingMaterial = encasingMaterial
}
This fails to compile because the designated initializer is not initializing all the stored properties this subclass introduces during phase 1.
// Init #2a - Designated
init(model: String, serialNumber: String, reusable: Bool, encasingMaterial: String) {
self.encasingMaterial = encasingMaterial
self.model = model + "-X"
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
This errors out because the subclass designated initializer is not allowed to initialize any properties not introduced by the same subclass. This code attempts to initialize model
, which was introduced by RocketComponent
, not Tank
.
Un-inheriting Initializers
Both instantiations are attempting to create a Tank
using initializers that belong to RocketComponent
, which is Tank
‘s superclass. The problem is that RocketComponent
‘s initializers only know how to initialize the properties introduced by RocketComponent
. These initializers have no visibility into properties introduced by subclasses. Therefore RocketComponent
‘s initializers are incapable of fully initializing a Tank
, even though Tank
is a subclass.
Even though automatic initializer inheritance is allowed in Objective-C, it’s clearly not ideal because Tank might assume that encasingMaterial is always set to a real string value. While this probably won’t cause a crash, it might cause unintended side effects. Swift does not allow this situation because it’s not safe, so as soon as you build a designated initializer for a subclass, the subclass stops inheriting all initializers, both designated and convenience.
Initialization Funnel
Convenience initializers delegate across to other convenience initializers until delegating to a designated initializer.
Also, a convenience initializer in a subclass cannot delegate up to a superclass convenience initializer. Convenience initializers can only delegate across the class in which they are defined.
Designated initializers in subclasses must delegate up to a designated initializer from the immediate superclass.
A designated initializer in a subclass cannot delegate up to a superclass convenience initializer.
Override Initializers
// Init #2b - Designated
override init(model: String, serialNumber: String, reusable: Bool) {
self.encasingMaterial = "Aluminum"
super.init(model: model, serialNumber: serialNumber, reusable: reusable)
}
Designated initializers are supposed to be very basic assignments of values to all stored properties, and normally take in an initial value for each property. Any time you need an initializer that simplifies initialization by having fewer arguments and/or doing some pre-processing on property values, you should make that a convenience initializer. That’s why they’re called convenience initializers, after all: they make initialization more convenient than using a designated initializer.
When overriding a superclass designated initializer, you can either make it a designated initializer or a convenience initializer.
// Init #3d - Convenience
convenience override init(model: String, serialNumber: String, reusable: Bool) {
self.init(model: model, serialNumber: serialNumber, reusable: reusable,
encasingMaterial: "Aluminum", liquidType: "LOX")
}
// Init #3e - Convenience
convenience override init(model: String, serialNumber: String, reusable: Bool,
encasingMaterial: String) {
self.init(model: model, serialNumber: serialNumber,
reusable: reusable, encasingMaterial: "Aluminum")
}
网友评论