swift comparison

Compara AnyObjects en Swift sin lanzarlos a un tipo específico



comparison (9)

Un intento de comparar dos objetos de tipo AnyObject usando el operador ''=='' definido en el protocolo Equatable da como resultado un error de compilación en Swift. ¿Alguien encontró una manera de compilar tales objetos, sin saber el tipo real de objetos que se pueden usar para la conversión en falso?

El problema es, ¿cómo se comparan dos objetos arbitrarios para la igualdad de contenido? No hay una definición general de ello. Si los objetos eran instancias de NSObject , o al menos NSObjectProtocol , entonces hay isEqual: pero para los objetos que no lo son, ¿cómo puede hacerlo?

Un intento de comparar dos objetos de tipo AnyObject usando el operador ''=='' definido en el protocolo Equatable da como resultado un error de compilación en Swift. ¿Alguien encontró una manera de comparar tales objetos, sin saber el tipo real de objetos que se pueden usar para hacer downcasting?

El trasfondo de esta pregunta es que tengo un diccionario Diccionario <Cadena, AnyObject> donde se supone que se proporcionan los valores a través de un subíndice, y en algún momento debo comparar los valores del diccionario para asegurarme de que son únicos.

EDITAR Aquí hay un fragmento que demuestra el problema.

@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 } } }

Tenga en cuenta que el genérico como <T: Equatable> declarado en la definición de la clase y utilizado como un tipo de valor del diccionario no solucionará el problema ya que no se puede usar junto con la subclase NSManagedObject.


Esto debería funcionar bastante bien para la mayoría de los casos de uso:

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


Me da un poco de vergüenza sugerir esto, pero me encontré con esto también. Mi situación era que estaba comparando los Dictionaries origen JSON y, de hecho, vivían en 2 NSManagedObjectContexts diferentes, NSManagedObjectContexts lo que los objetos no serían los mismos, aunque los valores podrían ser los mismos.

De todos modos, lo que hice fue simplemente crear cadenas a partir de los valores y compararlos. Es un truco, pero en mi caso fue para propósitos de prueba y funciona.

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 }


Mira la implementación:

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

esto significa que el compilador debe saber que los parámetros del lado izquierdo y del lado derecho deben ser del mismo tipo. Entonces, la función que estás implementando debería verse así:

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

Editar:

Con tus diccionarios debería ser algo como esto:

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

Edición 2:

Un ejemplo:

func compareFunc <T: Equatable>(dic1: [String : T], dic2 : [String : T]) -> Bool { return dic1["key"] == dic2["key"] } compareFunc(["key": "value"], ["key": "value"])


No creo que esto sea posible. El problema es que Equatable se define con un método que toma

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

y no hay manera de que, en tiempo de compilación, el compilador pueda encontrar la función correcta (ya que no conoce los tipos de antemano).

La única forma en que podría hacer esto es si tuviera una idea de los tipos que estaba comparando. Entonces podrías concretamente llamar la función de igualdad apropiada para cada tipo:

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; } }

Es interesante que C #, por ejemplo, solucione esto definiendo la interfaz de IComparable como teniendo un método:

int CompareTo(Object obj)

Esto permite que cada objeto de IComparable compare con cualquier otro objeto (pero la función tiene que hacer siempre su propia comprobación de tipos aquí, para asegurarse de que obj es del tipo correcto).


Si desea usar esto en Objective-C, ¿por qué no hace que las properties un Dictionary<String, NSObject> ? Después de todo, NSObject tiene isEqual y de todos modos no puede poner tipos primitivos en un NSDictionary .

Así se vería así:

@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 } } }


Swift 3 no funciona con === para comparación de referencia si los objetos son de tipo Any . La razón es que Any puede contener enteros y flotadores o cualquier otra cosa que no sea en realidad un objeto. Así que tendrás que NSObject a NSObject para comparar 2 elementos. Me gusta

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


Utilice el operador ===, de la documentación de Swift:

Swift también proporciona dos operadores de identidad (=== y! ==), que utiliza para probar si dos referencias de objeto se refieren a la misma instancia de objeto.


Utilizo estos métodos cuando necesito comparar valores que no son comparables, pero sí su contenido. La magia está en el cierre de la comparación. Realmente simple de escribir cuando se quiere enfocar en otras cosas que hacer que todas las clases sean comparables.

/*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 } }