Optional chaining

Have you ever been working with Xcode when you’ve gotten a prompt from the compiler that something is wrong and you are supposed to add ! to a property? The compiler is telling you that you are dealing with an optional value and it is suggesting that you deal with this optional value by force unwrapping it.

Sometimes force unwrapping is just fine. If you are dealing with @IBOutlets in your UI, you know that those UI elements must exist, because if they don’t there is something terribly wrong with your application. In general, force unwrap is appropriate only when an optional must contain a value. In all other cases, you’re asking for trouble!

class Pet {

var breed: String?

init(breed: String? = nil) { self.breed = breed

}

}

class Person { let pet: Pet

init(pet: Pet) { self.pet = pet

}

}

let delia = Pet(breed: "pug") let olive = Pet()

let janie = Person(pet: olive)

let dogBreed = janie.pet.breed! // This is bad! Will cause a crash!

In this simple example, Olive was not given a breed. She was a rescue from the pound, so her breed is unknown. But she’s still a sweetheart.

If you assume that her breed has been set and force unwrap this property, it will cause the program to crash. There’s a better way of handling this situation:

if let dogBreed = janie.pet.breed { print("Olive is a (dogBreed)")

} else {

print("Olive's breed is unknown.")

}

So far this is pretty standard optional handling, but you can take advantage of this structure to do some pretty complex operations. This can be incredibly helpful if you have a lot of complicated data structures with a lot of optional properties.

Comment out what you have so far and start over with the following types:

class Toy {

enum Kind { case ball case zombie case bone case mouse

}

enum Sound { case squeak case bell

}

let kind: Kind let color: String

var sound: Sound?

init(kind: Kind, color: String, sound: Sound? = nil) { self.kind = kind

self.color = color self.sound = sound

}

}

class Pet { enum Kind {

case dog case cat

case guineaPig

}

let name: String let kind: Kind

let favoriteToy: Toy?

init(name: String, kind: Kind, favoriteToy: Toy? = nil) { self.name = name

self.kind = kind self.favoriteToy = favoriteToy

}

}

class Person { let pet: Pet?

init(pet: Pet? = nil) { self.pet = pet

}

}

A lot of raywenderlich.com team members own pets — but not all. Some pets have a favorite toy and others don’t. Even further into this, some of these toys make noise and others don’t.

For example, Tammy Coron has an evil cat that is plotting to kill her.

This cat’s favorite toy to chew on (besides Tammy) is a catnip mouse. This toy doesn’t make any noise.

Ray has another team member, Felipe Marsetti, who lives in a condo and isn’t allowed to have pets.

let janie = Person(pet: Pet(name: "Delia", kind: .dog, favoriteToy: Toy(kind: .ball, color: "Purple", sound: .bell)))

let tammy = Person(pet: Pet(name: "Evil Cat Overlord", kind: .cat, favoriteToy: Toy(kind: .mouse, color: "Orange")))

let felipe = Person()

You want to check to see if any of the team members has a pet with a favorite toy that makes a sound. You can use optional chaining for this; it’s a quick way to walk through a chain of optionals by adding a ? after every property or method that can return nil. If any of the values in the chain was nil, the result will be nil as well. So instead of having to test every optional along the chain, you simply test the result!

For example:

if let sound = janie.pet?.favoriteToy?.sound { print("Sound (sound)")

} else {

print("No sound.")

}

Janie’s pet fulfills all of the conditions and therefore the sound can be accessed. Try this with Tammy and Felipe:

if let sound = tammy.pet?.favoriteToy?.sound { print("Sound (sound)")

} else {

print("No sound.")

}

if let sound = felipe.pet?.favoriteToy?.sound {

print("Sound (sound)")

} else {

print("No sound.")

}

During each stage of this chain, the compiler checks whether or not each object contains each optional property. Since Tammy’s cat’s toy does not have a sound, the process bails out after favoriteToy?. Since Felipe doesn’t have a pet at all, the process bails out after pet?.

This is an awful lot of repetitive code. What if you wanted to iterate through the entire array of team members to find this information?

results matching ""

    No results matching ""