Unwrapping Multiple Optionals in If Statement

unwrapping multiple optionals in if statement

Great news. Unwrapping multiple optionals in a single line is now supported in Swift 1.2 (XCode 6.3 beta, released 2/9/15).

No more tuple/switch pattern matching needed. It's actually very close to your original suggested syntax (thanks for listening, Apple!)

if let email = emailField?.text, password = passwordField?.text {

}

Another nice thing is you can also add where for a "guarding condition":

var email: String? = "baz@bar.com"
var name: String? = "foo"

if let n = name, e = email where contains(e, "@") {
println("name and email exist, email has @")
}

Reference: XCode 6.3 Beta Release Notes

Swift unwrap multiple optionals depending on each other in one if statement

If you only wish to work with the (possible) instance inner variable b (type B) of a (type A), you could use optional chaining in a single optional binding clause

class A {
var optionalB : B?
}

class B {}

var optionalA : A?

// in case you needn't actually use optionalA, but only its
// possibly non-nil instance member `optionalB`
if let b = optionalA?.optionalB {
//do something with b
}

Unwrapping and checking multiple optionals on single line

If you don't need v and s inside of the body of the if, you can just do the comparison directly:

if jsonResult["valid"] as? Int == 1 && jsonResult["status"] as? String == "ok" {
// Do something
}

Swift unwrapping for multiple optionals

If you're attempting to print the non-nil values as a comma-separated list, then I think @MartinR's suggestion of using flatMap() is the best:

let one: String?
let two: String?
let three: String?

one = "one"
two = nil
three = "three"

let nonNils = [one, two, three].flatMap { $0 }
if !nonNils.isEmpty {
print(nonNils.joinWithSeparator(","))
}

Output:

one,three

Using multiple let-as within a if-statement in Swift

Update for Swift 3:

The following will work in Swift 3:

if let latitudeDouble = latitude as? Double, let longitudeDouble = longitude as? Double {
// latitudeDouble and longitudeDouble are non-optional in here
}

Just be sure to remember that if one of the attempted optional bindings fail, the code inside the if-let block won't be executed.

Note: the clauses don't all have to be 'let' clauses, you can have any series of boolean checks separated by commas.

For example:

if let latitudeDouble = latitude as? Double, importantThing == true {
// latitudeDouble is non-optional in here and importantThing is true
}

Swift 1.2:

Apple may have read your question, because your hoped-for code compiles properly in Swift 1.2 (in beta today):

if let latitudeDouble = latitude as? Double, longitudeDouble = longitude as? Double {
// do stuff here
}

Swift 1.1 and earlier:

Here's the good news - you can totally do this. A switch statement on a tuple of your two values can use pattern-matching to cast both of them to Double at the same time:

var latitude: Any! = imageDictionary["latitude"]
var longitude: Any! = imageDictionary["longitude"]

switch (latitude, longitude) {
case let (lat as Double, long as Double):
println("lat: \(lat), long: \(long)")
default:
println("Couldn't understand latitude or longitude as Double")
}

Update: This version of the code now works properly.

Unwrapping multiple Swift optionals

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.)

Using if let... with many expressions

Update for Swift 1.2

Since Swift 1.2, if let allows unwrapping multiple optionals, so you can now just write this, as in your example:

if let x = someDict[someKey], y = someDict[someOtherKey] { … }

You can even interleave conditions such as:

if let x = someDict[someKey] where x == "value", y = someDict[someOtherKey] { … }

This used to be valid before Swift 1.2

Here's how you would do it without an ugly force-upwrapping:

switch (dict["a"], dict["b"]) {
case let (.Some(a), .Some(b)):
println("match")
default:
println("no match")
}

Still pretty verbose, actually.

This works because an optional type of the form Type? is actually shorthand for Optional<Type>, which is an enum that looks roughly like this:

enum Optional<T> {
case None
case Some(T)
}

You can then use pattern matching as for any other enum.

Edit: I've seen people write helper functions like this one (sorry for the lack of attribution, I don't remember where I saw it):

func unwrap<A, B>(a: A?, b: B?) -> (A, B)? {
switch (a, b) {
case let (.Some(a), .Some(b)):
return (a, b)
default:
return nil
}
}

Then you can keep using the if let construct, namely like this:

if let (a, b) = unwrap(dict["a"], dict["b"]) {
println("match: \(a), \(b)")
} else {
println("no match")
}

Can you safely unwrap nested optionals in swift in one line?

You could do it like so:

if let title = view.annotation?.title as? String {

}

view.annotation?.title is a double optional string: String?? since both the property annotation of an MKAnnotationView, and its own property title, are optional.


You could also use the guard statement like so:

guard let title = view.annotation?.title as? String else {
return
}
//use title in the rest of the scope

you could also use a switch statement :

switch title {
case .some(.some(let t)):
//use the title here
print(t)
default:
break
}


Related Topics



Leave a reply



Submit