Compare AnyObjects in Swift without casting them to a specific type

An attempt to compare two objects of AnyObject type using '==' operator defined in Equatable protocol result in a compile error in Swift. Did anyone find a way to compare such objects, without knowing the real type of objects that can be used for downcasting?

The background for this question is that I have a dictionary Dictionary<String, AnyObject> where values are supposed to be provided though a subscript, then at some point I need to compare the values in the dictionary to make sure that they are unique.

EDIT Here is a snippet demonstrating the issue.

@objc(FooObject)
public class FooObject: NSManagedObject {

    @NSManaged public var properties: Dictionary<String, AnyObject>

    public subscript(property: String) -> AnyObject? {
        get {
            return properties[property]
        }
        set(newValue) {

            for propertyValue in properties.values {
                if propertyValue == newValue { // This line is not compiling: Cannot invoke '==' with AnyObject
                    println("Values in are expected to be unique!")
                    // Throw an exception here ...
                }
            }

            properties[property] = newValue
        }
    }
}

Note that generic like <T:Equatable> declared in the class definition and used as a value type of the dictionary won't solve the issue as it cannot be used in conjunction with NSManagedObject subclass.

Answers


Use the === operator, from the Swift documentation:

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


I don't think this is possible. The problem is that Equatable is defined with a method that takes

func ==(a: T, b: T)

and there's no way, at compile-time, the compiler can find the correct function (as it doesn't know the types ahead of time).

The only way you could do this is if you had some idea of the types you were comparing. Then you could concretely call the appropriate equality function for each type:

func compare(a: AnyObject, b: AnyObject) {    
    if let va = a as? Int, vb = b as? Int            {if va != vb {return false}}
    else if let va = a as? String, vb = b as? String {if va != vb {return false}}
    else if let va = a as? Bool, vb = b as? Bool     {if va != vb {return false}}
            ...
    else {
        // not a type we expected
        return false;
    }
}

It's interesting that C#, for instance, gets around this by defining the IComparable interface as having a method:

int CompareTo(Object obj)

This allows every IComparable object to compare itself with any other object (but the function has to always do its own type-checking here, to make sure obj is of the correct type).


Swift 3 doesn't work with === for reference compare if the objects are of type Any. The reason is Any may contain integer and float or any other thing that is not in fact an object. So you will have to cast it to NSObject to compare 2 items. Like

if (obj1 as! NSObject) === (obj2 as! NSObject)
{
//logic goes here
}

This should work quite well for most use cases:

func equalAny<BaseType: Equatable>(lhs: Any, rhs: Any, baseType: BaseType.Type) -> Bool {
    guard
        let lhsEquatable = lhs as? BaseType,
        let rhsEquatable = rhs as? BaseType
        else { return false }
    return lhsEquatable == rhsEquatable
}

var a: Any = "a" as Any
var b: Any = "b" as Any

equalAny(lhs: a, rhs: b, baseType: NSObject.self) // false
b = "a"
equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true

a = CGFloat(5)
b = 5

equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true

a = ["hello": "world"]
b = ["hello": "world"]

equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true

Look at the implementation:

/// Returns true if these arrays contain the same elements.
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool

this means that the compiler need to know that the left hand side and the right hand side parameters must be of the same type. So the function you are implementing should look like this:

func compareFunc <T: Equatable>(par1: T, par2: T) {
    ...
    if par1 == par2 {
        ...
    }
    ...
}

Edit:

With your dictionaries should be something like this:

func compareFunc <T: Equatable>(dic1: [String : T], dic2: [String : T]) {
    ...
    if dic1[yourKey] == dic2[yourKey] {
        ...
    }
    ...
}

Edit 2:

An example:

func compareFunc <T: Equatable>(dic1: [String : T], dic2 : [String : T]) -> Bool {

    return dic1["key"] == dic2["key"]
}

compareFunc(["key": "value"], ["key": "value"])

If you want to use this in Objective-C, why not make properties a Dictionary<String, NSObject>? After all, NSObject has isEqual and you can't put primitive types into a NSDictionary anyway.

So it would look like this:

@objc(FooObject)
public class FooObject: NSManagedObject {

    @NSManaged public var properties: Dictionary<String, NSObject>

    public subscript(property: String) -> NSObject? {
        get {
            return properties[property]
        }
        set(newValue) {

            for propertyValue in properties.values {
                if propertyValue == newValue { // This line is not compiling: Cannot invoke '==' with AnyObject
                    print("Values in are expected to be unique!")
                    // Throw an exception here ...
                }
            }

            properties[property] = newValue
        }
    }
}

An attempt to compare two objects of AnyObject type using '==' operator defined in Equatable protocol result in a compile error in Swift. Did anyone found a way to compere such objects, without knowing the real type of objects that can be used for down casting?

The problem is, how do you compare two arbitrary objects for content equality? There is no general definition of it. If the objects were instances of NSObject, or at least NSObjectProtocol, then there is isEqual:, but for objects that are not, how can you do it?


I use these methods when I need to do comparing of values that are not equatable, but their content is. The magic is in the compare closure. Really simple to write when you want to focus on other things than making every class equatable.

/*Pre requsite: Temp and AnotherTemp must be string convertible*/
let a:AnyObject = "1"
let b:AnyObject = Temp("2")
let b:AnyObject = AnotherTemp(3)
let arr:[AnyObject] = [a,b,c]

Utils.has(arr,2,{"\($0)") == $1})//true or false

class Utils{    
    /**
     * EXAMPLE: ["a","b","c"].has("b",{$0 == $1})//true
     */
    static func has<T,V>(_ variables:[T],_ match:V,_ method:(T,V)->Bool) -> Bool  where V:Equatable{
        return first(variables, match, method) != nil
    }
    /**
     * Think of this method as: firstOccurence of something
     * Returns the first item that matches PARAM: match according to the constraints in PARAM: method
     * EXAMPLE: ["a","b","c"].first("b",{$0 == $1})//b
     * EXAMPLE: [("a",0),("b",1)].first("b",{$0.0 == $1}).1//b
     * EXAMPLE: [(id:"a",val:0),(id:"b",val:1)].first("b",{$0.id == $1}).val//b
     * NOTE: This method should have an extension, but making an extension for two generic variables proved difficult, more research needed, for now use the ArrayParser.first method call
     * NOTE: you could do: arr.forEach{/*assert logic goes here*/} but forEach can't return early so you are forced to iterate the entire list
     */
    static func first<T,V>(_ variables:[T],_ match:V,_ method:(T,V)->Bool) -> T?  where V:Equatable{
        for item in variables{
            if(method(item,match)){
                return item
            }
        }
        return nil
    }
}

I'm kind of embarrassed to even suggest this, but I just ran into this too. My scenario was that I was comparing JSON sourced Dictionaries and they in fact lived in 2 different NSManagedObjectContexts so the objects would not be the same, though the values might be.

Anyway what I did was simply to create Strings from whatever the values were and compare those. It's a hack, but in my case was for testing purposes and it works.

set(newValue) {
    for propertyValue in properties.values {
        if String(describing:propertyValue) == String(describing:newValue){ 
            println("Values in are expected to be unique!")
            // Throw an exception here ...
        }
    }
    properties[property] = newValue
}

Need Your Help

How can I align an element to the right in the FrameLayout?

android android-xml android-framelayout

I have a FrameLayout which contains 2 views , the second is something like a Close Button (X) and i want to position it on the right.

Are Layout Directives supported by Angular 2 Material Design Components?

angular flexbox material-design

I'm trying to use the Angular2 Material Design components, and I can't get any of the layout directives to work. Example: