devios1
devios1

Reputation: 38025

Most elegant way to compare two optionals in Swift

I have two AnyObject? variables that I would like to compare for reference equality:

var oldValue: AnyObject?
var newValue: AnyObject?
...
if oldValue != newValue {
    changed = true
}

This doesn't work though, as I apparently cannot compare two optionals directly. I want the behavior as if I were comparing ids in Objective-C, that is:

Is there an elegant way to write this in Swift (ideally without having to write a custom extension)?

This is the best I've come up with:

if !(oldValue != nil && newValue != nil && oldValue == newValue)

Not very pretty. :(

Upvotes: 20

Views: 14881

Answers (6)

NeilC
NeilC

Reputation: 11

I believe this is a Swift 5 version of Keith's older solution however this is on generics. If, for example, the thing you are comparing is String? it will consider that String? = nil is NOT the same as String? = "" although both are "empty" at a semantic level

func ==? <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
    if let lhs = lhs,
       let rhs = rhs {
        return lhs == rhs
    } else { return lhs == nil && rhs == nil }
}
func ==? <T: AnyObject>(lhs: T?, rhs: T?) -> Bool {
    if let lhs = lhs,
       let rhs = rhs {
        return lhs === rhs
    } else { return lhs == nil && rhs == nil }
}
infix operator ==?: ComparisonPrecedence

Upvotes: 1

Nitesh Borad
Nitesh Borad

Reputation: 4663

I liked @Keith's solution. But I think it is not written in Swift 4, as I can not compile it with Swift 4 compiler.

So I have converted his code to Swift 4 version here.

Remember, if you're using higher version of Swift language than Swift 4.1, then this answer is of no need as it provides this feature by default. You can refer here for more details.

Swift 4 version of @Keith's code:

infix operator ==? : ComparisonPrecedence

func ==? <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
    if let lhs = lhs, let rhs = rhs {
        return lhs == rhs
    } else {
        return lhs == nil && rhs == nil
    }
}

func ==? <T: AnyObject>(lhs: T?, rhs: T?) -> Bool {
    if let lhs = lhs, let rhs = rhs {
        return lhs === rhs
    } else {
        return lhs == nil && rhs == nil
    }
}

Upvotes: 9

nikans
nikans

Reputation: 2555

You can overload == operator for some Comparable type

public func ==<T: SomeType>(lhs: T?, rhs: T?) -> Bool {
    switch (lhs,rhs) {
    case (.some(let lhs), .some(let rhs)):
        return lhs == rhs
    case (.none, .none):
        return true
    default:
        return false
    }
}

Or use === comparison for AnyObject, though, personally I would prefer not to use AnyObject in the first place.

Upvotes: 0

Keith
Keith

Reputation: 141

I define a custom infix operator with a function for both reference types and value types.

func ==? <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
    if let lhs = lhs, rhs = rhs {
        return lhs == rhs
    } else{ return lhs == nil && rhs == nil }
}
func ==? <T: AnyObject>(lhs: T?, rhs: T?) -> Bool {
    if let lhs = lhs, rhs = rhs {
        return lhs === rhs
    } else{ return lhs == nil && rhs == nil }
}
infix operator ==? { associativity left precedence 130 }

var aString: String? = nil
var bString: String? = nil
print(aString ==? bString) // true

aString = "test"
bString = "test"
print(aString ==? bString) // true

aString = "test2"
bString = "test"
print(aString ==? bString) // false

aString = "test"
bString = nil
print(aString ==? bString) // false

class TT {}
let x = TT()

var aClass: TT? = TT()
var bClass: TT? = TT()
print(aClass ==? bClass) // false

aClass = TT()
bClass = nil
print(aClass ==? bClass) // false

aClass = nil
bClass = nil
print(aClass ==? bClass) // true

aClass = x
bClass = x
print(aClass ==? bClass) // true

Upvotes: 3

sbarow
sbarow

Reputation: 2819

You can use !==

From The Swift Programming Language

Swift also provides two identity operators (=== and !==), which you use to test wether two objects references both refer to the same object instance.

Some good examples and explanations are also at Difference between == and ===

On @PEEJWEEJ point, doing the following will result in false

var newValue: AnyObject? = "String"
var oldValue: AnyObject? = "String"

if newValue === oldValue {
   print("true")
} else {
   print("false")
}

Upvotes: 15

GetSwifty
GetSwifty

Reputation: 7746

Assuming you're using Comparable entities, this will work on anything:

func optionalsAreEqual<T: Comparable>(firstVal: T?, secondVal: T?) -> Bool{

    if let firstVal = firstVal, secondVal = secondVal {
        return firstVal == secondVal
    }
    else{
        return firstVal == nil && secondVal == nil
   }
}

It's not exactly short and sweet, but it's expressive, clear, and reusable.

Upvotes: 15

Related Questions