问题
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.
回答1:
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.
回答2:
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).
回答3:
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
}
回答4:
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
回答5:
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"])
回答6:
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
}
回答7:
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
}
}
}
回答8:
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?
回答9:
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
}
}
来源:https://stackoverflow.com/questions/25901105/compare-anyobjects-in-swift-without-casting-them-to-a-specific-type