Optional chaining in Swift is a means to safely access properties of potential nil
values using the
?.
operator. This concept is also known in other languages, particularly in
Groovy or
Spring EL
which provide the «safe navigation operator».
However, there is a subtle difference between Groovy's and Swift's implementation.
To recap shortly: Swift has the concept of
optional types.
An optional variable or property might be
nil
(it has no value). In contrast, non-optional types must have a value. Always. The compiler will
check that you follow this rule. The concept itself is great (in Java for example there is no language
construct to ensure that certain reference types will never become null
).
Optional types in Swift are marked with a question mark ?
after the type.
When accessing the value of an optional type one has to manage explicitly the case that it might be
nil
(using opt != nil
checks or
optional binding
if let opt = opt ...
).
Or you can use Optional Chaining. You put a question mark between an optional value and a property, method, or subscript access. Just like this
let foo1 = bar?.prop
let foo2 = bar?.method()
let foo3 = bar?[0]
If bar
appears to be nil
then all of the foo
s will also be nil
.
The results will always be optional values, even if the property prop
or the method
method()
are declared as non-optional for bar
.
No surprises so far.
OK, let's consider these definitions (see also the examples in the original Swift documentation):
class Person {
var residence: Residence = Residence()
}
class Residence {
var rooms = [Room]()
}
class Room {
// ...
}
Now suppose you have an optional person named Mary:
let mary: Person? = ...
Then you can safely access Mary's residence as follows (I have added the type declarations in the following snippets):
let marysFlat: Residence? = mary?.residence
So marysFlat
is an optional value again. Accessing the rooms
array then can be done with
let rooms: [Room]? = marysFlat?.rooms
So far so good, but what if you want to access Mary's rooms with one property access chain? I have tried the following (as it would be correct in Groovy):
let rooms: [Room]? = mary?.residence?.rooms
I have got an error stating
Cannot use optional chaining on non-optional value of type 'Residence'
Wait, what? Wasn't mary?.residence
supposed to be an optional value?
Funnily enough, the following works
let rooms: [Room]? = (mary?.residence)?.rooms
It turns out that the correct expression (or better: property access chain) must be:
let rooms: [Room]? = mary?.residence.rooms
No question mark between residence
and .rooms
. Indeed, the property residence
in Person
is non-optional (and if you have a person then it will always have a non-nil residence).
Apparently the two expressions (mary?.residence)?.rooms
and mary?.residence?.rooms
don't
represent the same data structures. And the Swift compiler will in a way cut the evaluation of the
property chain short. I am not quite sure yet which concept seems to be more intuitive:
the Groovy way or the Swift way.
By the way: Apparently some Groovy developers made the same error in reasoning, but in the other direction.
I'm interested in your feedback. Send me a mail or ping me on twitter.