Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should I compare an optional value to nil?

Quite often, you need to write code such as the following:

if someOptional != nil {     // do something with the unwrapped someOptional e.g.            someFunction(someOptional!) } 

This seems a bit verbose, and also I hear that using the ! force unwrap operator can be unsafe and best avoided. Is there a better way to handle this?

like image 917
Airspeed Velocity Avatar asked Apr 18 '15 12:04

Airspeed Velocity


People also ask

How do you know if optional is nil?

You can use if statement and compare optional with nil to find out whether a optional contains a value or not. You can use the comparison operator "equal to" operator ( == ) or the "not equal to" operator ( !=

What is the difference between optional none and nil?

There is no difference, as . none (basically it's Optional. none ) and nil are equivalent to each other. The use of nil is more common and is the recommended convention.

What will happen if you try to unwrap an optional that contains nil like so?

Unwrap an optional type with the nil coalescing operator If a nil value is found when an optional value is unwrapped, an additional default value is supplied which will be used instead. You can also write default values in terms of objects.

What should we use for unwrapping value inside optional?

A common way of unwrapping optionals is with if let syntax, which unwraps with a condition. If there was a value inside the optional then you can use it, but if there wasn't the condition fails. For example: if let unwrapped = name { print("\(unwrapped.


Video Answer


1 Answers

It is almost always unnecessary to check if an optional is not nil. Pretty much the only time you need to do this is if its nil-ness is the only thing you want to know about – you don’t care what’s in the value, just that it’s not nil.

Under most other circumstances, there is a bit of Swift shorthand that can more safely and concisely do the task inside the if for you.

Using the value if it isn’t nil

Instead of:

let s = "1" let i = Int(s)  if i != nil {     print(i! + 1) } 

you can use if let:

if let i = Int(s) {     print(i + 1) } 

You can also use var:

if var i = Int(s) {     print(++i)  // prints 2 } 

but note that i will be a local copy - any changes to i will not affect the value inside the original optional.

You can unwrap multiple optionals within a single if let, and later ones can depend on earlier ones:

if let url = NSURL(string: urlString),        data = NSData(contentsOfURL: url),        image = UIImage(data: data) {     let view = UIImageView(image: image)     // etc. } 

You can also add where clauses to the unwrapped values:

if let url = NSURL(string: urlString) where url.pathExtension == "png",    let data = NSData(contentsOfURL: url), image = UIImage(data: data) { etc. } 

Replacing nil with a default

Instead of:

let j: Int if i != nil {     j = i } else {     j = 0 } 

or:

let j = i != nil ? i! : 0 

you can use the nil-coalescing operator, ??:

// j will be the unwrapped value of i, // or 0 if i is nil let j = i ?? 0 

Equating an optional with a non-optional

Instead of:

if i != nil && i! == 2 {     print("i is two and not nil") } 

you can check if optionals are equal to non-optional values:

if i == 2 {     print("i is two and not nil") } 

This also works with comparisons:

if i < 5 { } 

nil is always equal to other nils, and is less than any non-nil value.

Be careful! There can be gotchas here:

let a: Any = "hello" let b: Any = "goodbye" if (a as? Double) == (b as? Double) {     print("these will be equal because both nil...") } 

Calling a method (or reading a property) on an optional

Instead of:

let j: Int if i != nil {     j = i.successor() } else {    // no reasonable action to take at this point    fatalError("no idea what to do now...") } 

you can use optional chaining, ?.:

let j = i?.successor() 

Note, j will also now be optional, to account for the fatalError scenario. Later, you can use one of the other techniques in this answer to handle j’s optionality, but you can often defer actually unwrapping your optionals until much later, or sometimes not at all.

As the name implies, you can chain them, so you can write:

let j = s.toInt()?.successor()?.successor() 

Optional chaining also works with subscripts:

let dictOfArrays: ["nine": [0,1,2,3,4,5,6,7]] let sevenOfNine = dictOfArrays["nine"]?[7]  // returns {Some 7} 

and functions:

let dictOfFuncs: [String:(Int,Int)->Int] = [       "add":(+),       "subtract":(-) ]  dictOfFuncs["add"]?(1,1)  // returns {Some 2} 

Assigning to a property on an optional

Instead of:

if splitViewController != nil {     splitViewController!.delegate = self  } 

you can assign through an optional chain:

splitViewController?.delegate = self 

Only if splitViewController is non-nil will the assignment happen.

Using the value if it isn’t nil, or bailing (new in Swift 2.0)

Sometimes in a function, there’s a short bit of code you want to write to check an optional, and if it’s nil, exit the function early, otherwise keep going.

You might write this like this:

func f(s: String) {     let i = Int(s)     if i == nil { fatalError("Input must be a number") }     print(i! + 1) } 

or to avoid the force unwrap, like this:

func f(s: String) {     if let i = Int(s) {         print(i! + 1)     }     else {          fatalErrr("Input must be a number")     } } 

but it’s much nicer to keep the error-handling code at the top by the check. This can also lead to unpleasant nesting (the "pyramid of doom").

Instead you can use guard, which is like an if not let:

func f(s: String) {     guard let i = Int(s)         else { fatalError("Input must be a number") }      // i will be an non-optional Int     print(i+1) } 

The else part must exit the scope of the guarded value, e.g. a return or fatalError, to guarantee that the guarded value will be valid for the remainder of the scope.

guard isn’t limited to function scope. For example the following:

var a = ["0","1","foo","2"] while !a.isEmpty  {     guard let i = Int(a.removeLast())         else { continue }      print(i+1, appendNewline: false) } 

prints 321.

Looping over non-nil items in a sequence (new in Swift 2.0)

If you have a sequence of optionals, you can use for case let _? to iterate over all the non-optional elements:

let a = ["0","1","foo","2"] for case let i? in a.map({ Int($0)}) {     print(i+1, appendNewline: false) } 

prints 321. This is using the pattern-matching syntax for an optional, which is a variable name followed by ?.

You can also use this pattern matching in switch statements:

func add(i: Int?, _ j: Int?) -> Int? {     switch (i,j) {     case (nil,nil), (_?,nil), (nil,_?):         return nil     case let (x?,y?):         return x + y     } }  add(1,2)    // 3 add(nil, 1) // nil 

Looping until a function returns nil

Much like if let, you can also write while let and loop until nil:

while let line = readLine() {     print(line) } 

You can also write while var (similar caveats to if var apply).

where clauses also work here (and terminate the loop, rather than skipping):

while let line = readLine()  where !line.isEmpty {     print(line) } 

Passing an optional into a function that takes a non-optional and returns a result

Instead of:

let j: Int if i != nil {     j = abs(i!) } else {    // no reasonable action to take at this point    fatalError("no idea what to do now...") } 

you can use optional’s map operator:

let j = i.map { abs($0) } 

This is very similar to optional chaining, but for when you need to pass the non-optional value into the function as an argument. As with optional chaining, the result will be optional.

This is nice when you want an optional anyway. For example, reduce1 is like reduce, but uses the first value as the seed, returning an optional in case the array is empty. You might write it like this (using the guard keyword from earlier):

extension Array {     func reduce1(combine: (T,T)->T)->T? {          guard let head = self.first             else { return nil }          return dropFirst(self).reduce(head, combine: combine)     } }  [1,2,3].reduce1(+) // returns 6 

But instead you could map the .first property, and return that:

extension Array {     func reduce1(combine: (T,T)->T)->T? {         return self.first.map {             dropFirst(self).reduce($0, combine: combine)         }     } } 

Passing an optional into a function that takes an optional and returns a result, avoiding annoying double-optionals

Sometimes, you want something similar to map, but the function you want to call itself returns an optional. For example:

// an array of arrays let arr = [[1,2,3],[4,5,6]] // .first returns an optional of the first element of the array // (optional because the array could be empty, in which case it's nil) let fst = arr.first  // fst is now [Int]?, an optional array of ints // now, if we want to find the index of the value 2, we could use map and find let idx = fst.map { find($0, 2) } 

But now idx is of type Int??, a double-optional. Instead, you can use flatMap, which “flattens” the result into a single optional:

let idx = fst.flatMap { find($0, 2) } // idx will be of type Int?  // and not Int?? unlike if `map` was used 
like image 136
Airspeed Velocity Avatar answered Sep 24 '22 21:09

Airspeed Velocity