Initializers in swift part-2: Failable Initializers in swift
This is the part two of my “Initializers in swift” article.
Here is the first part: Initializers in swift part-1 : (intro, convenience and designated intializers)
Let’s start:
Failable Initializers :
We cannot always assume that the initialization will always succeed for a struct, enum or class. It can fail for several reasons. It is sometimes useful to define a class, structure, or enumeration for which initialization can fail.
You write a failable initializer by placing a question mark after the
init
keyword (init?
).
Note: You cannot define a failable and a nonfailable initializer with the same parameter types and names.
A failable initializer creates an optional value of the type it initializes. You write return nil
within a failable initializer to indicate a point at which initialization failure can be triggered. ie; if a condition fails, you can return nil
.
IMPORTANT: In swift, the initializers won’t return anything. But objective -C does. In swift, You write
return nil
to trigger an initialization failure, you do not use thereturn
keyword to indicate initialization success.
Example: We have a failable initializer for converting Double
to Int
. This is a standard method which returns an optional Int
or nil
.
// failable initializerlet wholeNumber: Double = 12345.0if let valueMaintained = Int(exactly: wholeNumber) {print(“\(wholeNumber) conversion to Int maintains value of \(valueMaintained)”)}
Consider another example:
struct Animal {let species: Stringinit?(species: String) {if species.isEmpty { return nil }self.species = species}}
You can use this failable initializer to try to initialize a new Animal
instance and to check if initialization succeeded:
let someCreature = Animal(species: "Giraffe")// someCreature is of type Animal?, not Animalif let giraffe = someCreature {print("An animal was initialized with a species of \(giraffe.species)")}// Prints "An animal was initialized with a species of Giraffe"
Failable Initializers for Enumerations
Read my article about enums and raw values here.
You can use a failable initializer to select an appropriate enumeration case based on one or more parameters. The initializer can then fail if the provided parameters do not match an appropriate enumeration case.
enum TemperatureUnit {case kelvin, celsius, fahrenheitinit?(symbol: Character) {switch symbol {case "K":self = .kelvincase "C":self = .celsiuscase "F":self = .fahrenheitdefault:return nil}}
__________________________guard let fahrenheitUnit = TemperatureUnit(symbol: "X") else {print("This is not a defined temperature unit, so initialization failed.")}// Prints "This is not a defined temperature unit, so initialization failed."
Failable Initializers for Enumerations with Raw Values
Enumerations with raw values automatically receive a failable initializer, init?(rawValue:)
, that takes a parameter called rawValue
of the appropriate raw-value type and selects a matching enumeration case if one is found, or triggers an initialization failure if no matching value exists.
enum TemperatureUnit: Character {case kelvin = "K", celsius = "C", fahrenheit = "F"}let fahrenheitUnit = TemperatureUnit(rawValue: "F")if fahrenheitUnit != nil {print("This is a defined temperature unit, so initialization succeeded.")}// Prints "This is a defined temperature unit, so initialization succeeded."
Propagation of Initialization Failure
If your subclass is having a failable initializer which in turn call its superclass failable designated initializer, then if either of the initialization failed means the entire initialization process fails immediately, and no further initialization code is executed.
class Product {let name: Stringinit?(name: String) {if name.isEmpty { return nil }self.name = name}}class CartItem: Product {let quantity: Intinit?(name: String, quantity: Int) {if quantity < 1 { return nil }self.quantity = quantitysuper.init(name: name)}}
_____________________if let zeroShirts = CartItem(name: "shirt", quantity: 0) { // failsprint("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")} else {print("Unable to initialize zero shirts")}// Prints "Unable to initialize zero shirts"______________________if let oneUnnamed = CartItem(name: "", quantity: 1) { // failsprint("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")} else {print("Unable to initialize one unnamed product")}// Prints "Unable to initialize one unnamed product"
Overriding a Failable Initializer
You can override a superclass failable initializer in a subclass, just like any other initializer. Alternatively, you can override a superclass failable initializer with a subclass nonfailable initializer. This enables you to define a subclass for which initialization cannot fail, even though initialization of the superclass is allowed to fail.
Note that if you override a failable superclass initializer with a nonfailable subclass initializer, the only way to delegate up to the superclass initializer is to force-unwrap the result of the failable superclass initializer.
You can override a failable initializer with a nonfailable initializer but not the other way around.
The init! Failable Initializer
You typically define a failable initializer that creates an optional instance of the appropriate type by placing a question mark after the init
keyword (init?
). Alternatively, you can define a failable initializer that creates an implicitly unwrapped optional instance of the appropriate type. Do this by placing an exclamation mark after the init
keyword (init!
) instead of a question mark.
You can delegate from init?
to init!
and vice versa, and you can override init?
with init!
and vice versa. You can also delegate from init
to init!
, although doing so will trigger an assertion if the init!
initializer causes initialization to fail.
Where to go from here:
Initializers in swift part-3: Required Initializers in swift
If you enjoyed reading this post and found it useful, please share and recommend it so others can find it 💚💚💚💚💚💚 !!!!
Thanks!!