Lets say I have two variables, both optionals:
var a:Int? = 42
var b:Int? = 13
I have a condition where it's OK to proceed as long as these are not BOTH currently nil. I thoughtlessly put together something like:
guard let _ = a, let _ = b else { return }
I was absentmindedly thinking the conditions would be OR'ed, rather than AND'ed. Obviously that was wrong. The question then becomes, is there an idiomatic/preferred way to test that? Or do I just regress to the basics:
if a == nil && b == nil { return }
Aside
If I use the message extensions added by this post, then I might happily write something like
guard a.notNil || b.notNil else { return }
Which is about is close as I can come to "make certain (guard) that a is not nil or b is not nil"
A guard is an if, really, so you can do this the same way. This is clear and uses guard, which seems to be part of the fun. I present OR and AND options so you can pick one.
func doItOr(a: Int?, b:Int?) {
guard (a != nil || b != nil) else { return }
print("either A and B is not nil");
}
func doItAnd(a: Int?, b:Int?) {
guard (a != nil && b != nil) else { return }
print("both A and B are not nil");
}
doItOr(nil, b: nil)
doItOr(nil, b: 5)
doItOr(4, b: 5) // prints
doItAnd(nil, b: nil)
doItAnd(nil, b: 5)
doItAnd(nil, b: nil)
doItAnd(4, b: 5) // prints
it's OK to proceed as long as these are not BOTH currently nil
Your question poses two quite different possible conditions, so I will assume, for purposes of discussion, that you mean this one, namely "not both currently nil". Okay, then: "Not" is !. "Both" is &&. So, like this:
guard !(a == nil && b == nil) else {return}
If you really don't need to bind a or b as non-optionals or discern which of the two is non-nil, I believe the idiomatic approach would still be to use the if statement.
I generally use guard to narrow the specificity of my parameters by binding optionals, downcasting, etc.
You don't seem to be interested in doing that here. You just want to check them, so if seems to appropriately express that.
Related
In Swift 3, this is a compile error, if I use > or <
let a: Int?
guard a > 0 else {return}
guard a < 0 else {return}
Compile error:
Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
But it's okay if I compare with == or !=
let a: Int?
guard a == 0 else {return}
guard a != 0 else {return}
It makes perfect sense for the equality operator to support optionals, because it's absolutely clear that for any integer valued variable i:
nil == nil
nil != i
i != nil
i == i if and only if their values are the same
On the other hand, it's not clear how comparison to nil should act:
Is i less than nil?
If I want to sort an array so that all the nils come out at the end, then I would want i to be less than nil.
But if I want to sort an array so that all the nils come out at the start, then I would want i to be greater than nil.
Since either of these are equally valid, it wouldn't make sense for the standard library to favor one over the other. It's left to the programmer to implement whichever comparison makes sense for their use-case.
Here's a toy implementation that generates a comparison operator to suite either case:
func nilComparator<T: Comparable>(nilIsLess: Bool) -> (T?, T?) -> Bool {
return {
switch ($0, $1) {
case (nil, nil): return false
case (nil, _?): return nilIsLess
case (_?, nil): return !nilIsLess
case let (a?, b?): return a < b
}
}
}
let input = (0...10).enumerated().map {
$0.offset.isMultiple(of: 2) ? Optional($0.element) : nil
}
func concisePrint<T>(_ optionals: [T?]) -> String {
return "[" + optionals.map { $0.map{ "\($0)?" } ?? "nil" }.joined(separator: ", ") + "]"
}
print("Input:", concisePrint(input))
print("nil is less:", concisePrint(input.sorted(by: nilComparator(nilIsLess: true))))
print("nil is more:", concisePrint(input.sorted(by: nilComparator(nilIsLess: false))))
Output:
Input: [0?, nil, 2?, nil, 4?, nil, 6?, nil, 8?, nil, 10?]
nil is less: [nil, nil, nil, nil, nil, 0?, 2?, 4?, 6?, 8?, 10?]
nil is more: [0?, 2?, 4?, 6?, 8?, 10?, nil, nil, nil, nil, nil]
Optional equality works logically, comparison doesn't.
5 == 5 = true
5 == nil = false
5 == 6 = false
nil == nil = true
Those all make sense, but these don't:
6 > 5 = true
5 > 5 = false
5 > nil = ??
nil > 5 = ??
This type of comparison does not have a simple answer, nor will that answer be the same depending on the use case.
This is because Int and Int? are 2 different things.
According to the documentation, Int has overloads for < and > and some other operators, while Optional only has overloads for == and !=, see the documentation on Optional, the section talking about Comparing Optional Values.
I found out, that I can write this code:
func optionalReturn() -> Int? {
// do sth. and return maybe an Int, otherwise:
return nil
}
if let x = optionalReturn(), x > 5 {
print("test exists and is greater then 5")
}
Now I'm standing in front of the following problem: I want to have an if, that handles two cases:
case 1: x is not existing, OR
case 2: x is existing, but is greater than value 5
This is not working:
if x == nil || (let x = optionalReturn(), x > 5) {
...
}
Is sth. like this possible in Swift?
UPDATE
The above code was simplified to show my problem, but my case is a little bit different, because my called function doesn't return an optional Int, it returns an optional struct:
struct TestStruct {
var x: Int
var y: Int
}
func optionalReturn() -> TestStruct? {
// do sth. and maybe return a TestStruct(....), otherwise:
return nil
}
let test = optionalReturn()
if test == nil || ...?
UPDATE 2
In my first update I had a mistake. Thanks to Christik, who mentioned it in the comments, my code had a mistake and would have worked without it. I accepted his answer as a solution, but my first idea was right as well: Swift skips the other if-conditions, if the first OR-condition is true. So this code works as well:
...
if test == nil || (test!.x > 5 && test!.y > 6) {
print("I am here")
}
You can use the nil coalescing operator here, and give x a value greater than 5, if its nil:
if (x ?? 6) > 5 {
// do your thing
}
In regards to the question update, you can use map on the optional to achieve your goal:
if x.map({ $0.x > 5 && $0.y > 6 }) ?? true {
// do your thing
}
map has the advantage that it avoids the forced unwrap.
In Swift 3, this is a compile error, if I use > or <
let a: Int?
guard a > 0 else {return}
guard a < 0 else {return}
Compile error:
Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
But it's okay if I compare with == or !=
let a: Int?
guard a == 0 else {return}
guard a != 0 else {return}
It makes perfect sense for the equality operator to support optionals, because it's absolutely clear that for any integer valued variable i:
nil == nil
nil != i
i != nil
i == i if and only if their values are the same
On the other hand, it's not clear how comparison to nil should act:
Is i less than nil?
If I want to sort an array so that all the nils come out at the end, then I would want i to be less than nil.
But if I want to sort an array so that all the nils come out at the start, then I would want i to be greater than nil.
Since either of these are equally valid, it wouldn't make sense for the standard library to favor one over the other. It's left to the programmer to implement whichever comparison makes sense for their use-case.
Here's a toy implementation that generates a comparison operator to suite either case:
func nilComparator<T: Comparable>(nilIsLess: Bool) -> (T?, T?) -> Bool {
return {
switch ($0, $1) {
case (nil, nil): return false
case (nil, _?): return nilIsLess
case (_?, nil): return !nilIsLess
case let (a?, b?): return a < b
}
}
}
let input = (0...10).enumerated().map {
$0.offset.isMultiple(of: 2) ? Optional($0.element) : nil
}
func concisePrint<T>(_ optionals: [T?]) -> String {
return "[" + optionals.map { $0.map{ "\($0)?" } ?? "nil" }.joined(separator: ", ") + "]"
}
print("Input:", concisePrint(input))
print("nil is less:", concisePrint(input.sorted(by: nilComparator(nilIsLess: true))))
print("nil is more:", concisePrint(input.sorted(by: nilComparator(nilIsLess: false))))
Output:
Input: [0?, nil, 2?, nil, 4?, nil, 6?, nil, 8?, nil, 10?]
nil is less: [nil, nil, nil, nil, nil, 0?, 2?, 4?, 6?, 8?, 10?]
nil is more: [0?, 2?, 4?, 6?, 8?, 10?, nil, nil, nil, nil, nil]
Optional equality works logically, comparison doesn't.
5 == 5 = true
5 == nil = false
5 == 6 = false
nil == nil = true
Those all make sense, but these don't:
6 > 5 = true
5 > 5 = false
5 > nil = ??
nil > 5 = ??
This type of comparison does not have a simple answer, nor will that answer be the same depending on the use case.
This is because Int and Int? are 2 different things.
According to the documentation, Int has overloads for < and > and some other operators, while Optional only has overloads for == and !=, see the documentation on Optional, the section talking about Comparing Optional Values.
I want to use Swift (not Objective-C runtime) Reflection to create a method like this:
func valueFor(property:String, of object:Any) -> Any? {
...
}
To some extent, I can do this using:
func valueFor(property:String, of object:Any) -> Any? {
let mirror = Mirror(reflecting: object)
return mirror.descendant(property)
}
With
class TestMe {
var x:Int!
}
let t = TestMe()
t.x = 100
let result = valueFor(property: "x", of: t)
print("\(result); \(result!)")
This prints out what I'd expect:
Optional(100); 100
When I do:
let t2 = TestMe()
let result2 = valueFor(property: "x", of: t2)
print("\(result2)")
The output is:
Optional(nil)
This might seem reasonable, except that if I do:
var x:Int!
print("\(x)")
This prints out:
nil
and not Optional(nil). The bottom line is that I'm having difficulty programmatically determining that the value of t2.x is nil using my valueFor method.
If I continue the above code with:
if result2 == Optional(nil)! {
print("Was nil1")
}
if result2 == nil {
print("Was nil2")
}
Neither of these print statements output anything.
When I put a breakpoint into Xcode and look at the value of result2 with the debugger, it shows:
▿ Optional<Any>
- some : nil
So, my question is: How can I determine if the original member variable was nil using the result from valueFor?
Additional1:
If I do:
switch result2 {
case .some(let x):
// HERE
break
default:
break
}
and put a breakpoint at HERE, the value of x turns out to be nil. But, even if I assign it to an Any?, comparing it to nil is not true.
Additional2:
If I do:
switch result2 {
case .some(let x):
let z:Any? = x
print("\(z)")
if z == nil {
print("Was nil3")
}
break
default:
break
}
This prints out (only):
Optional(nil)
I find this especially odd. result2 prints out exactly the same thing!
This is a bit of a hack, but I think it's going to solve the problem for me. I'm still looking for better solutions:
func isNilDescendant(_ any: Any?) -> Bool {
return String(describing: any) == "Optional(nil)"
}
func valueFor(property:String, of object:Any) -> Any? {
let mirror = Mirror(reflecting: object)
if let child = mirror.descendant(property), !isNilDescendant(child) {
return child
}
else {
return nil
}
}
well, i know it has been 4 years, but I am on Xcode 12 and still facing the same issue. since this question seems to be unanswered, I will add what worked for me.
func valueFor(property: String, of object: Any) -> Any? {
let optionalPropertyName = "some"
let mirror = Mirror(reflecting: object)
if let child = mirror.descendant(property) {
if let optionalMirror = Mirror(reflecting: child), optionalMirror.displayStyle == DisplayStyle.optional {
return optionalMirror.descendant(optionalPropertyName)
} else {
return child
}
} else {
return nil
}
}
by using Mirror to check for optional and then extract the optional using "some" you get back either a true object or nil. when this is returned to the caller via the Any? return, you are now able to nil check the value and have that work appropriately.
I have two different scenarios where I need to test the "optionality" of an optional type. I have not been able to figure how to explicitly test if the variable is a .None or a .Some other than with an unwieldy switch statement. How can I test for Someness with an if statement?
Scenario 1
I am writing an address formatter and my inputs are a number of String? types. In this example a simple test for (str != nil) will work. However, since my other need is when dealing with a 'double optional' and a nil test can't distinguish between .Some(.None) and .None a solution to this problem will solve that problem too.
Here's a version that works using a switch
let address1:String? = "123 Main St"
let address2:String? = nil
let apt:String? = "101"
let components = [address1, address2, apt].filter( { (c) -> Bool in
switch c {
case .Some: return true
case .None: return false
}
}).map { return $0! } //Had to map because casting directly to [String] crashes
print(", ".join(components)) //"123 Main St, 101"
What's I'd like to see is something like with an if:
let nice = ["123 Main St", nil, "303"].filter { (c) -> Bool in
return (c == .Some)
}
print(", ".join(nice))
Scenario 2
This is where a nil test won't work. If something is a String?? it can be any of .None, .Some(.None), or .Some(.Some(String)). In my case, the variable is carrying the recordID from an api call which might either be missing entirely (.None), a value (.Some(.Some("ABDEFG")), or explicitly NULL (.Some(.None)).
let teamNoneNone: String?? = .None
let teamSomeNone: String?? = .Some(.None)
let teamSomeSome: String?? = "My favorite local sportsball team"
if teamNoneNone == nil {
print("teamNoneNone is nil but is it .None? We don't know!") //prints
} else {
print("teamNoneNone is not nil")
}
if teamSomeNone == nil {
print("teamSomeNone is nil")
} else {
print("teamSomeNone is not nil but is it .Some(.None)? We don't know!") //prints
}
if teamSomeSome == nil {
print("teamSomeSome is nil but is it .None? We don't know!")
} else {
print("teamSomeSome is not nil but is it .Some(.None) or .Some(.Some())? We don't know!") //prints
}
Via another SO post I found a workaround like this, but it's not very clear what's happening to a casual reader:
if let team: String? = teamSomeNone {
print("teamSomeNone is Some(.None)") //prints
} else {
print("teamSomeNone is .Some(.Some())")
}
if let tests if a value is .None, and if it isn’t, it unwraps it and binds it to a local variable within an if statement.
Using switch with .Some and .None is really a secondary way of handling optionals, if if let doesn’t cut it. But it almost always does, especially now you can do multiple if lets in a single statement, following the latest release of Swift 1.2 to production.
Wanting to filter out the nils in a collection is a common-enough task that Haskell has a standard function for it, called catMaybe. Here’s a version, which I’ll call catSome, that would do the trick in Swift:
func catSome<T>(source: [T?]) -> [T] {
var result: [T] = []
// iterate over the values
for maybe in source {
// if this value isn’t nil, unwrap it
if let value = maybe {
// and append it to the array
result.append(value)
}
}
return result
}
let someStrings: [String?] = ["123 Main St", nil, "101"]
catSome(someStrings) // returns ["123 Main St", "101"]
Doubly-wrapped optionals are a bit of a pain, so the best solution is to avoid them in the first place – often, via use of optional chaining or flatMap.
But if you do find yourself with some, and all you care about is the inner value, you can unwrap them using a double if let:
// later parts of the let can rely on the earlier
if let outer = teamSomeSome, teamName = outer {
println("Fully unwrapped team is \(teamName)")
}
If you want to explicitly know if a double-optional has an inner nil inside an outer value, but isn’t nil itself, you can use if let with a where clause:
if let teamSomeMaybe = teamSomeNone where teamSomeMaybe == nil {
// this will be executed only if it was .Some(.None)
println("SomeNone")
}
The where clause is an extra conditional that can be applied to the unwrapped value.