AnyObject try cast to Equatable

4.1k views Asked by At

I have an Equatable class

class Item: Equatable {
    var value: AnyObject?
    var title: String
    init(title: String, value: AnyObject?) {
        self.title = title
        self.value = value
    }
    //Equatable
    public static func ==(lhs: Item, rhs: Item) -> Bool {
        return ((lhs.title == rhs.title) && (lhs.value === rhs.value))
    }  
}

But I want cast try var value to Equatable, So that get soft equatable result

if let lValue = lhs.value as? Equatable,   // Error
   let rValue = rhs.value as? Equatable {  // Error
    valueEq = (lValue == rValue)
} else {
    valueEq = (lhs.value === rhs.value)
}

This code catch compilation error about Generic Equatable

How is I can do correct Equatable for this class?

UPD

I want use my Item in UITableViewCell in my storyboard. I Cant create generic UITableViewCell. And if I try do Item as Generic<T: Equatable> class, I will be forced to specify the types in my Cells,

var items: [Item<OnlyThisHashableClass>]

but I want use Item in Cells for any objects

2

There are 2 answers

0
EvGeniy Ilyin On BEST ANSWER

Easy way - class stay NonGeneric, Generic only init, and in GenericInit create isEquals method

class FieldItem: CustomStringConvertible, Equatable {
    let value: Any?
    let title: String
    private let equals: (Any?) -> Bool
    init<Value: Equatable>(title: String, value: Value?) {
        func isEquals(_ other: Any?) -> Bool {
            if let l = value, let r = other {
                if let r = r as? Value {
                    return l == r
                } else {
                    return false
                }
            } else {
                return true
            }
        }
        self.title = title
        self.value = value
        self.equals = isEquals
    }
    //CustomStringConvertible
    var description: String { get { return title } }
    //Equatable
    public static func ==(lhs: FieldItem, rhs: FieldItem) -> Bool {
        return ((lhs.title == rhs.title) && lhs.equals(rhs.value))
    }

}
1
Rob On

You can't cast AnyObject to an Equatable.

What you can do is define Item as a generic for which the value, Wrapped, must be Equatable:

class Item<Wrapped: Equatable> {
    var title: String
    var value: Wrapped

    init(title: String, value: Wrapped) {
        self.title = title
        self.value = value
    }
}

extension Item: Equatable {
    static func ==(lhs: Item, rhs: Item) -> Bool {
        return lhs.title == rhs.title && lhs.value == rhs.value
    }
}

And, let's imagine that you have some class, Foo, that (a) isn't equatable; (b) is something you want to wrap in an Item; and (c) you really want to define them to be equatable on the basis of the identity operator, ===. (I confess, I find that notion, which you call "soft equatable" fairly disturbing notion, but I won't go into that here.)

Anyway, you can just make your class Foo equatable on the basis of the identity operator:

extension Foo: Equatable {
    static func ==(lhs: Foo, rhs: Foo) -> Bool {
        return lhs === rhs
    }
}

Or, if you need to do this for many classes, you could even have a protocol for this identity-equality, and then your non-equatable classes could just conform to that:

protocol IdentityEquatable: class, Equatable { }

extension IdentityEquatable {
    static func ==(lhs: Self, rhs: Self) -> Bool {
        return lhs === rhs
    }
}

Then any classes that you want to wrap in an Item that aren't Equatable could adopt this identity-equatable behavior with a single line of code each:

extension Foo: IdentityEquatable { }
extension Bar: IdentityEquatable { }
extension Baz: IdentityEquatable { }

As an aside, SE-0143 has been approved and while not part of the language yet, offers the promise of Conditional Conformance in future Swift versions, namely:

class Item<Wrapped> {
    var title: String
    var value: Wrapped

    init(title: String, value: Wrapped) {
        self.title = title
        self.value = value
    }
}

extension Item: Equatable where Wrapped: Equatable {
    static func ==(lhs: Item, rhs: Item) -> Bool {
        return lhs.title == rhs.title && lhs.value == rhs.value
    }
}

In this case, Item would be Equatable if and only if the Wrapped value was Equatable. This isn't part of the language yet, but looks like it will be in a future version. It is an elegant solution to this problem (though not, admittedly, your "soft equatable" idea).