As práticas recomendadas para substituir isEqual: e hash

267

Como você substitui corretamente isEqual:no Objective-C? A "captura" parece ser que, se dois objetos forem iguais (conforme determinado pelo isEqual:método), eles deverão ter o mesmo valor de hash.

A seção Introspecção do Guia de fundamentos do cacau tem um exemplo de como substituir isEqual:, copiado da seguinte forma, para uma classe chamada MyWidget:

- (BOOL)isEqual:(id)other {
    if (other == self)
        return YES;
    if (!other || ![other isKindOfClass:[self class]])
        return NO;
    return [self isEqualToWidget:other];
}

- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
    if (self == aWidget)
        return YES;
    if (![(id)[self name] isEqual:[aWidget name]])
        return NO;
    if (![[self data] isEqualToData:[aWidget data]])
        return NO;
    return YES;
}

Ele verifica a igualdade do ponteiro, depois a igualdade de classe e, finalmente, compara os objetos usando isEqualToWidget:, que apenas verifica as propriedades namee data. O que o exemplo não mostra é como substituir hash.

Vamos supor que existem outras propriedades que não afetam a igualdade, digamos age. O hashmétodo não deve ser substituído de maneira que apenas namee dataafete o hash? E se sim, como você faria isso? Basta adicionar os hashes de namee data? Por exemplo:

- (NSUInteger)hash {
    NSUInteger hash = 0;
    hash += [[self name] hash];
    hash += [[self data] hash];
    return hash;
}

Isso é suficiente? Existe uma técnica melhor? E se você tiver primitivos, como int? Converta-os para NSNumberobter seu hash? Ou estruturas como NSRect?

( Peido do cérebro : originalmente escrevia "OR bit a bit" junto com |=.

Dave Dribin
fonte
2
if (![other isKindOfClass:[self class]])- Tecnicamente, isso significa que a igualdade não será comutativa. Ou seja, A = B não significa B = A (por exemplo, se uma é uma subclasse da outra)
Robert
O link da documentação está inoperante, agora arquivado no Introspection
jedwidz

Respostas:

111

Começar com

 NSUInteger prime = 31;
 NSUInteger result = 1;

Então, para cada primitivo que você fizer

 result = prime * result + var

Para objetos, você usa 0 para zero e, caso contrário, seu código de hash.

 result = prime * result + [var hash];

Para booleanos, você usa dois valores diferentes

 result = prime * result + ((var)?1231:1237);

Explicação e atribuição

Este não é o trabalho de tcurdt, e os comentários estavam pedindo mais explicações, por isso acredito que uma edição para atribuição é justa.

Esse algoritmo foi popularizado no livro "Effective Java", e o capítulo relevante pode ser encontrado online aqui . Esse livro popularizou o algoritmo, que agora é o padrão em vários aplicativos Java (incluindo o Eclipse). Derivou, no entanto, de uma implementação ainda mais antiga, atribuída de várias formas a Dan Bernstein ou Chris Torek. Esse algoritmo antigo flutuava originalmente na Usenet, e certas atribuições são difíceis. Por exemplo, há alguns comentários interessantes neste código do Apache (procure por seus nomes) que referenciam a fonte original.

Resumindo, este é um algoritmo de hash muito antigo e simples. Não é o mais eficiente e nem mesmo é comprovado matematicamente como um algoritmo "bom". Mas é simples e muitas pessoas o usam há muito tempo com bons resultados, por isso tem muito apoio histórico.

tcurdt
fonte
9
De onde veio o 1231: 1237? Eu também o vejo no Boolean.hashCode () de Java. Isso é mágico?
9789 David Leonard
17
É da natureza dos algoritmos de hash que haverá colisões. Então, eu não entendo o seu ponto, Paul.
precisa saber é o seguinte
85
Na minha opinião, esta resposta não responde à pergunta real (práticas recomendadas para substituir o hash do NSObject). Ele apenas fornece um algoritmo de hash específico. Além disso, a escassez da explicação dificulta a compreensão sem um conhecimento profundo do assunto e pode resultar em pessoas que a usam sem saber o que estão fazendo. Não entendo por que essa pergunta tem tantos votos positivos.
Ricardo Sanchez-Saez 12/10
6
1º problema - (int) é pequeno e fácil de transbordar, use o NSUInteger. Segundo problema - Se você continuar multiplicando o resultado por cada hash de variável, o resultado será estourado. por exemplo. [NSString hash] cria grandes valores. Se você tiver mais de 5 variáveis, é fácil transbordar com esse algoritmo. Isso resultará no mapeamento de tudo para o mesmo hash, o que é ruim. Veja minha resposta: stackoverflow.com/a/4393493/276626
Paul Solt
10
@PaulSolt - O estouro não é um problema ao gerar um hash, é uma colisão. Mas o estouro não necessariamente torna mais provável a colisão, e sua declaração sobre o estouro fazendo com que tudo seja mapeado para o mesmo hash está simplesmente incorreto.
DougW
81

Estou pegando o Objective-C, então não posso falar especificamente para esse idioma, mas nos outros idiomas que uso se duas instâncias são "Iguais", elas devem retornar o mesmo hash - caso contrário, você terá todos tipos de problemas ao tentar usá-los como chaves em uma hashtable (ou qualquer coleção do tipo dicionário).

Por outro lado, se 2 instâncias não forem iguais, elas podem ou não ter o mesmo hash - é melhor se não tiverem. Essa é a diferença entre uma pesquisa O (1) em uma tabela de hash e uma pesquisa O (N) - se todos os seus hashes colidirem, você poderá achar que pesquisar sua tabela não é melhor do que pesquisar uma lista.

Em termos de práticas recomendadas, seu hash deve retornar uma distribuição aleatória de valores para sua entrada. Isso significa que, por exemplo, se você tem um dobro, mas a maioria dos seus valores tende a se agrupar entre 0 e 100, é necessário garantir que os hashes retornados por esses valores sejam distribuídos uniformemente por todo o intervalo possível de valores possíveis de hash . Isso melhorará significativamente seu desempenho.

Existem vários algoritmos de hash por aí, incluindo vários listados aqui. Eu tento evitar a criação de novos algoritmos de hash, pois podem ter grandes implicações no desempenho. Portanto, usar os métodos de hash existentes e fazer uma combinação bit a bit de algum tipo, como no exemplo, é uma boa maneira de evitá-lo.

Brian B.
fonte
4
+1 Excelente resposta, merece mais votos, especialmente porque ele realmente fala sobre "melhores práticas" e a teoria por trás de um importante hash (único) é importante.
Quinn Taylor
30

Um XOR simples sobre os valores de hash das propriedades críticas é suficiente 99% do tempo.

Por exemplo:

- (NSUInteger)hash
{
    return [self.name hash] ^ [self.data hash];
}

Solução encontrada em http://nshipster.com/equality/ por Mattt Thompson (que também fez referência a essa pergunta em seu post!)

Yariv Nissim
fonte
1
O problema com esta resposta é que ela não leva em consideração valores primitivos. E valores primitivos também podem ser importantes para o hash.
Vive
@Vive A maioria desses problemas é resolvida no Swift, mas esses tipos geralmente representam seu próprio hash, pois são primitivos.
Yariv Nissim
1
Enquanto você está certo em Swift, ainda existem muitos projetos escritos com objc. Como sua resposta é dedicada a objc, vale pelo menos uma menção.
Vive
XORing valores de hash juntos é um mau conselho, pois leva a muitas colisões de hash. Em vez disso, multiplique com um número primo e adicione como outras respostas.
fishinperto de 28/03
27

Achei este tópico extremamente útil, fornecendo tudo o que eu precisava para obter meus métodos isEqual:e hashimplementados com uma captura. Ao testar variáveis ​​de instância de objeto no isEqual:código de exemplo, use:

if (![(id)[self name] isEqual:[aWidget name]])
    return NO;

Isso repetidamente falhou ( ou seja , retornou NÃO ) sem erro, quando eu sabia que os objetos eram idênticos no meu teste de unidade. O motivo foi que uma das NSStringvariáveis ​​de instância era nula; portanto, a instrução acima foi:

if (![nil isEqual: nil])
    return NO;

e como nada responderá a qualquer método, isso é perfeitamente legal, mas

[nil isEqual: nil]

retorna nulo , que é NÃO , portanto, quando o objeto e o objeto em teste tivessem um objeto nulo, eles seriam considerados diferentes ( ou seja , isEqual:retornariam NÃO ).

Essa correção simples foi alterar a instrução if para:

if ([self name] != [aWidget name] && ![(id)[self name] isEqual:[aWidget name]])
    return NO;

Dessa forma, se os endereços forem os mesmos, a chamada do método será ignorada, independentemente de serem nulas ou ambas apontando para o mesmo objeto, mas se elas não forem nulas ou se apontarem para objetos diferentes, o comparador será chamado apropriadamente.

Espero que isso poupe a alguém alguns minutos de coçar a cabeça.

LavaSlider
fonte
20

A função hash deve criar um valor semi-exclusivo que provavelmente não colidirá ou corresponderá ao valor de hash de outro objeto.

Aqui está a função hash completa, que pode ser adaptada às variáveis ​​de instância de suas classes. Ele usa o NSUInteger em vez de int para compatibilidade em aplicativos de 64/32 bits.

Se o resultado se tornar 0 para objetos diferentes, você corre o risco de colidir hashes. A colisão de hashes pode resultar em um comportamento inesperado do programa ao trabalhar com algumas das classes de coleção que dependem da função de hash. Certifique-se de testar sua função de hash antes de usar.

-(NSUInteger)hash {
    NSUInteger result = 1;
    NSUInteger prime = 31;
    NSUInteger yesPrime = 1231;
    NSUInteger noPrime = 1237;

    // Add any object that already has a hash function (NSString)
    result = prime * result + [self.myObject hash];

    // Add primitive variables (int)
    result = prime * result + self.primitiveVariable; 

    // Boolean values (BOOL)
    result = prime * result + (self.isSelected?yesPrime:noPrime);

    return result;
}
Paul Solt
fonte
3
Uma dica aqui: eu prefiro evitar a sintaxe de pontos, então eu transformei sua instrução BOOL em (por exemplo) result = prime * result + [self isSelected] ? yesPrime : noPrime;. Descobri que isso estava sendo definido resultcomo (por exemplo) 1231, presumo que o ?operador tenha precedência. Corrigi o problema adicionando colchetes:result = prime * result + ([self isSelected] ? yesPrime : noPrime);
Ashley
12

A maneira mais fácil, porém ineficiente, é retornar o mesmo -hashvalor para cada instância. Caso contrário, sim, você deve implementar o hash baseado apenas em objetos que afetam a igualdade. Isso é complicado se você usar comparações relaxadas em-isEqual: (por exemplo, comparações de strings que não diferenciam maiúsculas de minúsculas). Para ints, geralmente você pode usar o próprio int, a menos que esteja comparando com os NSNumbers.

Não use | =, porém, ele irá saturar. Use ^ = em vez disso.

Curiosidade aleatória:, [[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]mas [[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]. (rdar: // 4538282, aberto desde 05 de maio de 2006)

Jens Ayton
fonte
1
Você está exatamente certo no | =. Realmente não quis dizer isso. :) + = e ^ = são bastante equivalentes. Como você lida com primitivas não inteiras como double e float?
Dave Dribin 31/10/08
Aleatório fato do divertimento: testá-lo em Snow Leopard ... ;-)
Quinn Taylor
Ele está correto sobre o uso de XOR em vez de OU para combinar campos em um hash. No entanto, não use o conselho de retornar o mesmo valor -hash para cada objeto - embora seja fácil, ele pode prejudicar severamente o desempenho de qualquer coisa que use o hash do objeto. O hash não precisa ser distinto para objetos que não são iguais, mas se você conseguir isso, não há nada parecido.
Quinn Taylor
O relatório de bug do radar aberto está fechado. openradar.me/4538282 O que isso significa?
perfil completo de JJD
JJD, o bug foi corrigido no Mac OS X 10.6, como Quinn sugeriu. (Observe que o comentário tem dois anos.)
Jens Ayton
9

Lembre-se de que você só precisa fornecer um hash igual quando isEqualverdadeiro. Quando isEqualé falso, o hash não precisa ser desigual, embora presumivelmente seja. Conseqüentemente:

Mantenha o hash simples. Escolha uma variável de membro (ou poucos membros) que seja a mais distinta.

Por exemplo, para CLPlacemark, apenas o nome é suficiente. Sim, existem 2 ou 3 distintos CLPlacemark com exatamente o mesmo nome, mas esses são raros. Use esse hash.

@interface CLPlacemark (equal)
- (BOOL)isEqual:(CLPlacemark*)other;
@end

@implementation CLPlacemark (equal)

...

-(NSUInteger) hash
{
    return self.name.hash;
}


@end

Observe que não me preocupo em especificar a cidade, o país etc. O nome é suficiente. Talvez o nome e CLLocation.

O hash deve ser distribuído uniformemente. Então você pode combinar a variável de vários membros usando o sinal de intercalação ^ (sinal xor)

Então é algo como

hash = self.member1.hash ^ self.member2.hash ^ self.member3.hash

Dessa forma, o hash será distribuído igualmente.

Hash must be O(1), and not O(n)

Então, o que fazer na matriz?

Mais uma vez, simples. Você não precisa fazer o hash de todos os membros da matriz. O suficiente para fazer o hash do primeiro elemento, último elemento, a contagem, talvez alguns elementos do meio, e é isso.

user4951
fonte
Os valores de hash XORing não fornecem uma distribuição uniforme.
fishinperto de 28/03
7

Espere, certamente uma maneira muito mais fácil de fazer isso é substituir primeiro - (NSString )description e fornecer uma representação em cadeia do estado do seu objeto (você deve representar todo o estado do seu objeto nessa cadeia).

Em seguida, forneça a seguinte implementação de hash:

- (NSUInteger)hash {
    return [[self description] hash];
}

Isso se baseia no princípio de que "se dois objetos de seqüência de caracteres forem iguais (conforme determinado pelo método isEqualToString:), eles deverão ter o mesmo valor de hash".

Fonte: Referência da Classe NSString

Jonathan Ellis
fonte
1
Isso pressupõe que o método de descrição será exclusivo. O uso do hash da descrição cria uma dependência, que pode não ser óbvia, e um risco maior de colisões.
Paul Solt
1
Marcado com +1. Essa é uma ideia incrível. Se você teme que as descrições causem colisões, você poderá substituí-las.
user4951
Obrigado Jim, não vou negar que isso é um pouco complicado, mas funcionaria em qualquer caso em que eu pudesse pensar - e como eu disse, desde que você substitua description, não vejo por que isso é inferior a qualquer uma das soluções mais votadas. Pode não ser a solução mais matematicamente elegante, mas deve fazer o truque. Como afirma Brian B. (resposta mais votada neste momento): "Tento evitar a criação de novos algoritmos de hash" - concordou! - Eu apenas hasho NSString!
Jonathan Ellis
Promovido porque é uma ideia interessante. No entanto, não vou usá-lo porque temo as alocações adicionais do NSString.
194 karwag
1
Esta não é uma solução genérica, pois para a maioria das classes descriptioninclui o endereço do ponteiro. Portanto, isso cria duas instâncias diferentes da mesma classe iguais a hash diferente, que violam a suposição básica de que dois objetos iguais têm o mesmo hash!
Diogo T
5

Os contratos iguais e hash são bem especificados e pesquisados ​​exaustivamente no mundo Java (consulte a resposta de @ mipardi), mas todas as mesmas considerações devem se aplicar ao Objective-C.

O Eclipse faz um trabalho confiável para gerar esses métodos em Java, então aqui está um exemplo do Eclipse portado manualmente para o Objective-C:

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if ([self class] != [object class])
        return false;
    MyWidget *other = (MyWidget *)object;
    if (_name == nil) {
        if (other->_name != nil)
            return false;
    }
    else if (![_name isEqual:other->_name])
        return false;
    if (_data == nil) {
        if (other->_data != nil)
            return false;
    }
    else if (![_data isEqual:other->_data])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = 1;
    result = prime * result + [_name hash];
    result = prime * result + [_data hash];
    return result;
}

E para uma subclasse YourWidgetque adiciona uma propriedade serialNo:

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if (![super isEqual:object])
        return false;
    if ([self class] != [object class])
        return false;
    YourWidget *other = (YourWidget *)object;
    if (_serialNo == nil) {
        if (other->_serialNo != nil)
            return false;
    }
    else if (![_serialNo isEqual:other->_serialNo])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = [super hash];
    result = prime * result + [_serialNo hash];
    return result;
}

Esta implementação evita algumas armadilhas de subclassificação no exemplo isEqual:da Apple:

  • O teste de classe da Apple other isKindOfClass:[self class]é assimétrico para duas subclasses diferentes de MyWidget. A igualdade precisa ser simétrica: a = b se e somente se b = a. Isso poderia ser facilmente corrigido alterando o teste para other isKindOfClass:[MyWidget class], então todas as MyWidgetsubclasses seriam mutuamente comparáveis.
  • O uso de um isKindOfClass:teste de subclasse impede que as subclasses sejam substituídas isEqual:por um teste de igualdade refinado. Isso ocorre porque a igualdade precisa ser transitiva: se a = be a = c, então b = c. Se uma MyWidgetinstância for comparada a duas YourWidgetinstâncias, essasYourWidget instâncias deverão ser comparadas entre si, mesmo que sejam serialNodiferentes.

A segunda questão pode ser resolvida considerando-se que os objetos são iguais apenas se pertencerem exatamente à mesma classe, daí o [self class] != [object class]teste aqui. Para classes de aplicativos típicas , essa parece ser a melhor abordagem.

No entanto, certamente há casos em que o isKindOfClass:teste é preferível. Isso é mais típico das classes de estrutura do que as classes de aplicativos. Por exemplo, qualquer um NSStringdeve comparar igual a qualquer outro NSStringcom a mesma sequência de caracteres subjacente, independentemente da distinção NSString/ NSMutableString, e também independentemente de quais classes privadas no NSStringcluster de classes estão envolvidas.

Nesses casos, isEqual:deve ter um comportamento bem definido e bem documentado, e deve ficar claro que as subclasses não podem substituir isso. Em Java, a restrição 'no override' pode ser imposta sinalizando os métodos equals e hashcode como final, mas o Objective-C não tem equivalente.

jedwidz
fonte
@ adubr Isso é coberto nos meus últimos dois parágrafos. Não é focal, como MyWidgetse entende por não ser um cluster de classes.
jedwidz
5

Isso não responde diretamente à sua pergunta (de forma alguma), mas eu usei o MurmurHash antes para gerar hashes: murmurhash

Acho que devo explicar o porquê: murmurhash é muito rápido ...

schwa
fonte
2
Uma biblioteca C ++ focada em hashes exclusivos para uma chave void * usando número aleatório (e também não se relaciona a objetos Objective-C) realmente não é uma sugestão útil aqui. O método -hash deve retornar um valor consistente a cada vez ou será totalmente inútil. Se o objeto for adicionado a uma coleção que chama -hash e retornar um novo valor sempre, duplicatas nunca serão detectadas e você também não poderá recuperar o objeto da coleção. Nesse caso, o termo "hash" é diferente do significado em segurança / criptografia.
Quinn Taylor
3
murmurhash is não é uma função hash criptográfica. Verifique seus fatos antes de postar informações incorretas. Murmurhash pode ser útil para fazer hash de classes personalizadas de objetivo-c (especialmente se você tiver muitos NSDatas envolvidos), porque é extremamente rápido. No entanto, garanto que talvez sugerir que não seja o melhor conselho para alguém "apenas pegar o objetivo-c", mas observe meu prefixo na resposta original à pergunta.
schwa
4

Também sou novato no Objetivo C, mas encontrei um excelente artigo sobre identidade versus igualdade no Objetivo C aqui . Pela minha leitura, parece que você pode manter a função hash padrão (que deve fornecer uma identidade única) e implementar o método isEqual para comparar valores de dados.

ceperry
fonte
Eu sou um novato da Cocoa / Objective C, e essa resposta e link realmente me ajudaram a analisar todas as coisas mais avançadas acima, até o resultado final - não preciso me preocupar com hashes - apenas implementando o método isEqual:. Obrigado!
John Gallagher
Não perca o link de @ ceperry. O artigo Equality vs Identityde Karl Kraft é realmente bom.
JJD
6
@ John: Eu acho que você deve reler o artigo. Diz claramente que "instâncias iguais devem ter valores de hash iguais". Se você substituir isEqual:, também deverá substituir hash.
171311 Steve Madsen
3

Quinn está errado ao dizer que a referência ao hash do sopro é inútil aqui. Quinn está certo de que deseja entender a teoria por trás do hash. O murmúrio destila grande parte dessa teoria em uma implementação. Vale a pena descobrir como aplicar essa implementação a esse aplicativo específico.

Alguns dos principais pontos aqui:

A função de exemplo de tcurdt sugere que '31' é um bom multiplicador porque é primo. É preciso mostrar que ser primo é uma condição necessária e suficiente. De fato, 31 (e 7) provavelmente não são primos particularmente bons porque 31 == -1% 32. Um multiplicador ímpar com cerca de metade dos bits definidos e metade dos bits limpos provavelmente será melhor. (A constante de multiplicação de hash de sopro possui essa propriedade.)

Esse tipo de função hash provavelmente seria mais forte se, após a multiplicação, o valor do resultado fosse ajustado por meio de um deslocamento e xor. A multiplicação tende a produzir os resultados de muitas interações de bits na extremidade superior do registro e resultados de baixa interação na extremidade inferior do registro. O deslocamento e xor aumentam as interações na extremidade inferior do registro.

Definir o resultado inicial como um valor em que cerca de metade dos bits são zero e cerca de metade dos bits são um também tenderia a ser útil.

Pode ser útil ter cuidado com a ordem em que os elementos são combinados. Provavelmente, deve-se primeiro processar booleanos e outros elementos em que os valores não sejam fortemente distribuídos.

Pode ser útil adicionar alguns estágios de embaralhamento de bits extras no final do cálculo.

Se o hash do sopro é realmente rápido ou não para este aplicativo é uma questão em aberto. O hash do sopro pré-mistura os bits de cada palavra de entrada. Várias palavras de entrada podem ser processadas em paralelo, o que ajuda os cpus em pipeline com vários problemas.


fonte
3

Combinando a resposta do @ tcurdt com a resposta do @ oscar-gomez para obter nomes de propriedades , podemos criar uma solução fácil para o isEqual e o hash:

NSArray *PropertyNamesFromObject(id object)
{
    unsigned int propertyCount = 0;
    objc_property_t * properties = class_copyPropertyList([object class], &propertyCount);
    NSMutableArray *propertyNames = [NSMutableArray arrayWithCapacity:propertyCount];

    for (unsigned int i = 0; i < propertyCount; ++i) {
        objc_property_t property = properties[i];
        const char * name = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:name];
        [propertyNames addObject:propertyName];
    }
    free(properties);
    return propertyNames;
}

BOOL IsEqualObjects(id object1, id object2)
{
    if (object1 == object2)
        return YES;
    if (!object1 || ![object2 isKindOfClass:[object1 class]])
        return NO;

    NSArray *propertyNames = PropertyNamesFromObject(object1);
    for (NSString *propertyName in propertyNames) {
        if (([object1 valueForKey:propertyName] != [object2 valueForKey:propertyName])
            && (![[object1 valueForKey:propertyName] isEqual:[object2 valueForKey:propertyName]])) return NO;
    }

    return YES;
}

NSUInteger MagicHash(id object)
{
    NSUInteger prime = 31;
    NSUInteger result = 1;

    NSArray *propertyNames = PropertyNamesFromObject(object);

    for (NSString *propertyName in propertyNames) {
        id value = [object valueForKey:propertyName];
        result = prime * result + [value hash];
    }

    return result;
}

Agora, em sua classe personalizada, você pode implementar facilmente isEqual:e hash:

- (NSUInteger)hash
{
    return MagicHash(self);
}

- (BOOL)isEqual:(id)other
{
    return IsEqualObjects(self, other);
}
johnboiles
fonte
2

Observe que, se você estiver criando um objeto que pode ser alterado após a criação, o valor do hash não será alterado se o objeto for inserido em uma coleção. Na prática, isso significa que o valor do hash deve ser corrigido a partir do ponto de criação do objeto inicial. Consulte a documentação da Apple no método -hash do protocolo NSObject para obter mais informações:

Se um objeto mutável for adicionado a uma coleção que usa valores de hash para determinar a posição do objeto na coleção, o valor retornado pelo método de hash do objeto não deve mudar enquanto o objeto estiver na coleção. Portanto, o método hash não deve confiar em nenhuma das informações de estado interno do objeto ou você deve garantir que as informações de estado interno do objeto não sejam alteradas enquanto o objeto estiver na coleção. Assim, por exemplo, um dicionário mutável pode ser colocado em uma tabela de hash, mas você não deve alterá-lo enquanto estiver lá. (Observe que pode ser difícil saber se um determinado objeto está ou não em uma coleção.)

Isso me parece uma completa loucura, pois potencialmente torna as pesquisas de hash muito menos eficientes, mas acho que é melhor errar por precaução e seguir o que a documentação diz.

user10345
fonte
1
Você está lendo os documentos hash errados - é essencialmente uma situação "ou-ou". Se o objeto for alterado, o hash geralmente também será alterado. Este é realmente um aviso ao programador de que, se o hash mudar como resultado da mutação de um objeto, a alteração do objeto enquanto ele reside em uma coleção que utiliza o hash causará um comportamento inesperado. Se o objeto precisar ser "mutável com segurança" em tal situação, você não terá outra opção a não ser tornar o hash não relacionado ao estado mutável. Essa situação em particular me parece estranha, mas certamente existem situações pouco frequentes em que ela se aplica.
21711 Quinn Taylor
1

Desculpe se eu arriscar soar um caixão completo aqui, mas ... ... ninguém se incomodou em mencionar que, para seguir as "melhores práticas", você definitivamente não deve especificar um método igual que NÃO levaria em consideração todos os dados pertencentes ao seu objeto de destino, por exemplo os dados são agregados ao seu objeto, em comparação com um associado, devem ser levados em consideração ao implementar iguais. Se você não quiser levar em consideração uma comparação, diga 'idade' em uma comparação, escreva um comparador e use-o para realizar suas comparações em vez de isEqual :.

Se você definir um método isEqual: que executa arbitrariamente a comparação de igualdade, você corre o risco de que esse método seja usado incorretamente por outro desenvolvedor, ou até por você mesmo, depois de esquecer o 'toque' na sua interpretação igual.

Portanto, embora este seja um ótimo questionário sobre hash, normalmente você não precisa redefinir o método de hash, provavelmente deve definir um comparador ad-hoc.

Thibaud de Souza
fonte