2012-02-20 13 views
7

Se produjo un pequeño problema interesante. Estaba escribiendo un método para filtrar una matriz a los objetos únicos:Conversión de NSArray a NSSet, las instancias de clases personalizadas se transfieren de manera incoherente

- (NSArray*)distinctObjectsByAddress { 
    NSSet* uniqueSet = [NSSet setWithArray:self]; 
    NSArray* retArray = [uniqueSet allObjects]; 

    return retArray; 
} 

y escribió una unidad de prueba para comprobar:

- (void)testDistinctObjectsByAddress5 { 
    Person* adam1 = [[Person alloc] initWithFirstName:@"adam" lastName:@"adam" andParent:nil]; 
    Person* adam2 = [[Person alloc] initWithFirstName:@"adam" lastName:@"adam" andParent:nil]; 

    testPersonArray = [NSArray arrayWithObjects:adam1,adam2, nil]; 

    NSArray* checkArray = [testPersonArray distinctObjectsByAddress]; 

    STAssertEquals([checkArray count], [testPersonArray count], @"Array %@ counts should match %@ %@",checkArray,adam1,adam2); 
} 

bastante simple. La parte interesante es que alrededor del 80-90% del tiempo pasa la prueba y de vez en cuando falla porque el método distinctObjectsByAddress solo devuelve un objeto. Pude rastrearlo hasta la llamada [NSSet setWithArray:self] pero también he podido verificar que los objetos de dos personas son dos objetos diferentes (al menos tienen una dirección diferente). Supongo que setWithArray: está haciendo una comparación básica de direcciones, pero no entiendo por qué a veces produce dos objetos como debería y en ocasiones solo produce uno.

Algo que acabo de probar estaba cambiando adam2 para que el nombre y el apellido no fueran exactamente lo mismo que adam1. Esto parece arreglar el error. ¿Esto apunta a algún tipo de optimización del compilador cuando los objetos son lógicamente iguales?

+4

Supongo que el problema es con Person y cómo implementa los métodos hash e igualdad que usa NSSet. –

+1

Utilizará 'isEqual:' como se define en el protocolo 'NSObject' para comparar objetos; ¿Lo has implementado o 'hash' en' Persona'? – Tommy

+1

Además del problema de hash, no estás probando explícitamente adam1 y adam2 por cero. Si ocasionalmente no se inicia, eso explicaría la falla de la prueba. – bneely

Respuesta

9

Estoy asumiendo que setWithArray sólo está haciendo una dirección básica comparar

Eso es incorrecto. NSSet utiliza los métodos -isEqual: y -hash en los objetos que se agregan a él. Depende de cómo se implementen en Persona o sus superclases.

Si el [person1 isEqual:person2] es de esperar que el conjunto contenga un objeto. Si no, entonces el conjunto debe contener dos objetos.

Supongo que la persona no sigue the rules en sus métodos -isEqual: y -hash. Lo más probable es que los dos objetos sean iguales, pero sus hashes no son iguales a como deberían ser. (Excepto por el 10-20% de las veces que tiene suerte.)

¿Esto apunta a algún tipo de optimización del compilador cuando los objetos son lógicamente iguales?

No, no hay optimización del compilador que fusionaría los dos objetos en uno solo.

3

Lo más probable es que no haya implementado hash para Person, y en ocasiones el mismo hash de objeto Person en dos cubos diferentes.

+0

hmm, anulé isEqual pero no hash, así que ese es probablemente el problema, desde entonces he eliminado el código NSSet ya que me pareció un poco 'hacky' y escribí el código para comparar las direcciones. Pero todavía estoy realmente interesado en por qué esto estaba sucediendo. Esta cosa de hash es probablemente, lo verifico y acepto si lo es. – ACBurk

+0

@ACBurk según los documentos "Si dos objetos son iguales, deben tener el mismo valor hash". (https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html # // apple_ref/occ/intfm/NSObject/isEqual:); la falla al implementar NSObject correctamente podría dar como resultado cualquier comportamiento. – Tommy

Cuestiones relacionadas