2012-02-20 4 views
7

Trasportato in un piccolo problema interessante. Stavo scrivendo un metodo per filtrare un array per gli oggetti unici:Conversione di NSArray in NSSet, trasferimento di istanze di classi personalizzate in modo incoerente

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

    return retArray; 
} 

e ha scritto uno unit test per controllare:

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

Abbastanza semplice. La parte interessante è che circa l'80-90% delle volte il test passa e ogni tanto fallisce perché il metodo distinctObjectsByAddress restituisce solo un oggetto. Sono stato in grado di rintracciarlo alla chiamata [NSSet setWithArray:self] ma sono anche stato in grado di verificare che gli oggetti a due persone siano due oggetti diversi (almeno hanno un indirizzo diverso). Suppongo che lo setWithArray: stia semplicemente facendo un confronto di base, ma non capisco perché a volte produca due oggetti come dovrebbe e talvolta ne produce solo uno.

Qualcosa che ho appena provato stava cambiando adam2 in modo che il nome e il cognome non corrispondessero esattamente allo adam1. Questo sembra risolvere l'errore. Questo indica una sorta di ottimizzazione del compilatore quando gli oggetti sono logicamente uguali?

+4

Direi che il problema è con Person e come implementa i metodi di hash e di uguaglianza utilizzati da NSSet. –

+1

Userà 'isEqual:' come definito dal protocollo 'NSObject' per confrontare gli oggetti; l'hai implementato o 'hash' su' Persona'? – Tommy

+1

Oltre al problema dell'hash, non stai testando esplicitamente adam1 e adam2 per zero. Se uno di tanto in tanto non riesce a inizializzare, ciò spiegherebbe il fallimento del test. – bneely

risposta

9

sto supponendo che setWithArray è solo facendo un indirizzo di base confronto

Questo è errato. NSSet utilizza i metodi -isEqual: e -hash per gli oggetti aggiunti. Dipende da come sono implementati in Person o nelle sue superclassi.

Se [person1 isEqual:person2] ci si aspetta che il set contenga un oggetto. In caso contrario, l'insieme dovrebbe contenere due oggetti.

La mia ipotesi è che Persona non segua the rules nei suoi metodi -isEqual: e -hash. Molto probabilmente, i due oggetti sono uguali, ma i loro hash non sono uguali come dovrebbero essere. (Tranne il 10-20% delle volte che stai diventando fortunato.)

Questo indica una sorta di ottimizzazione del compilatore quando gli oggetti sono logicamente uguali?

No, non c'è ottimizzazione del compilatore che unire i due oggetti in uno.

3

Molto probabilmente non è stato implementato hash per Person e talvolta gli identici oggetti Person hash in due diversi bucket.

+0

hmm, ho fatto override isEqual ma nessun hash, quindi questo è probabilmente il problema, da allora ho preso il codice NSSet perché sembrava un po '"hacky" e ho scritto il codice per confrontare gli indirizzi da solo. Ma sono ancora molto interessato al motivo per cui ciò stava accadendo. Questa cosa dell'hash è probabilmente, controllerò e accetto se lo è. – ACBurk

+0

@ACBurk per i documenti "Se due oggetti sono uguali, devono avere lo stesso valore di hash." (https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html # // apple_ref/occ/intfm/NSObject/isEqual:); l'incapacità di implementare correttamente NSObject potrebbe comportare validamente qualsiasi comportamento. – Tommy