Sinisa Drpa
Sinisa Drpa

Reputation: 917

Unwrapping multiple Swift optionals

I'm trying to rewrite part of ObjC code with Swift, a lot of cocoa object properties return optional value in Swift. Is there better pattern instead of this deep nesting:

  if let panel = self.window {
     if let screens = NSScreen.screens() {
        if  let screenRect = screens[0].frame {
           let statusRect = CGRectZero
              ...

Upvotes: 3

Views: 637

Answers (5)

Sergey Teryokhin
Sergey Teryokhin

Reputation: 229

Please read about optional chaining here: Linking Multiple Levels of Chaining

And you can play with this playground to look how does it work:

import UIKit


class AAA {
    var string: String? = nil
}

class AA {
    var aaa: [AAA?]? = nil
}

class A {
    var aa: AA? = nil
}


var a: A?

a = A()

/// Comment/Uncomment next lines
a?.aa = AA()
//a?.aa?.aaa = [AAA()]
a?.aa?.aaa = []
a?.aa?.aaa?.first??.string = "My String Value"

if let myString = a?.aa?.aaa?.first??.string {
    print("myString: \(myString)")
} else {
    print("myString does not acceptable")
}

And your code may look like:

if let panel = self.window, let statusRect = NSScreen.screens()?.first??.frame ?? CGRect.zero {
    /// your code to work with panel and statusRect
}

Upvotes: 0

Bogdan
Bogdan

Reputation: 597

As of Swift 1.2 you can do unwrapping on one line, as described in this answer:

unwrapping multiple optionals in if statement

Upvotes: 0

ColinE
ColinE

Reputation: 70142

I wrote a blog post a while back which explored a few different ways to unwrap multiple optionals.

You can create a function that unwraps multiple optionals in one go:

func if_let<T, U, V>(a: Optional<T>, b: Optional<U>,
  c: Optional<V>, fn: (T, U, V) -> ()) {
  if let a = a {
    if let b = b {
      if let c = c {
        fn(a, b, c)
      }
    }
  }
}

used as follows:

if_let(a, b, c) {
  a, b, c in
  println("\(a) - \(b) - \(c)")
}

Someone also proposed a neat solution using tuples, that can be found in this gist.

Upvotes: 1

Gregory Higley
Gregory Higley

Reputation: 16558

While Leonardo's answer is a good one, it can lead to exceptions unless you know the objects are non-optional, a better pattern is this, which should be regarded as pseudocode:

if let frame = NSScreen.screens()?.first?.frame {
   // Do something with frame.
}

In other words, use optional chaining (?.), but only use let with the very last part of the chain.

You could also create an operator like this if you want to chain optionals that are of a different type, in which only the value of the last one will be returned:

infix operator ??? { associativity left }

func ??? <T1, T2>(t1: T1?, t2: @autoclosure () -> T2?) -> T2? {
    return t1 == nil ? nil : t2()
}

if let frame = self.window ??? NSScreen.screens()?.first?.frame {
    // Do something with frame
}

This is, of course, inspired by Haskell's bind. But as fun as this is, I don't recommend it. I think it's clear as mud. (By the way, the difference between ??? and ?? is that the former does not require the lhs and rhs to be of the same type (or supertype), while the latter does. ??? always returns its rhs or nil.)

Upvotes: 2

Leo Dabus
Leo Dabus

Reputation: 236360

You can force unwrap it like this:

NSScreen.screens()!.first!.frame!
NSScreen.screens()!.first!.frame!.width
NSScreen.screens()!.first!.frame!.height


// safely unwraping it as required an mentioned by Gregory
if let frame = NSScreen.screens()?.first?.frame {
    println(frame)
}
if let width = NSScreen.screens()?.first?.frame?.width {
    println(width)
}
if let height = NSScreen.screens()?.first?.frame?.height {
    println(height)
}

Upvotes: 1

Related Questions