20

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:

  • true if both are nil
  • true if both have a value and the values are also equal
  • false otherwise

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. :(

devios1
  • 36,899
  • 45
  • 162
  • 260

6 Answers6

15

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.

GetSwifty
  • 7,568
  • 1
  • 29
  • 46
  • Though it's not exactly what I was looking for, thanks for posting this in case others are looking for a non-reference comparison. – devios1 Mar 17 '16 at 17:01
14

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")
}
Community
  • 1
  • 1
sbarow
  • 2,759
  • 1
  • 22
  • 37
  • 7
    FYI, I think if oldValue and newValue are objects, === will always be false unless they're actually set to the same object. – GetSwifty Mar 17 '16 at 17:10
8

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
    }
}
Mittul At TechnoBrave
  • 1,142
  • 3
  • 25
  • 70
Nitesh Borad
  • 4,583
  • 36
  • 51
3

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
Keith
  • 141
  • 2
  • 5
0

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.

nikans
  • 2,468
  • 1
  • 28
  • 35
0

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
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
NeilC
  • 1