Faça a igualdade e o hashing personalizados em Swift usarem uma única definição imutável de valor. Este passo a passo cria uma chave local de bloco cujo nome ignora diferenças entre maiúsculas e minúsculas, enquanto suas dimensões permanecem exatas; em seguida, verifica igualdade, associação a conjuntos e busca em dicionários.
Defina exatamente quando dois blocos são iguais
Suponha que você esteja criando um pequeno exercício gráfico para desktop no Mac. Você precisa agrupar descrições sintéticas de blocos por nome, largura e altura. A tarefa do leitor é implementar esse agrupamento de forma consistente: Orbit e ORBIT com as mesmas dimensões devem gerar chaves iguais; alterar qualquer uma das dimensões deve gerar uma chave diferente.
Escreva o contrato antes de implementar qualquer um dos protocolos. Neste exemplo, duas chaves são iguais quando seus nomes convertidos para minúsculas, suas larguras e suas alturas são iguais. As dimensões são unidades de desenho inteiras positivas. Os espaços iniciais e finais continuam significativos. Esta é uma regra de aplicação ilustrativa, não um requisito imposto pelo Swift.
| Entradas sintéticas | Relação esperada | Motivo |
|---|---|---|
Orbit, 40 × 20 e ORBIT, 40 × 20 |
Iguais | Diferenças entre maiúsculas e minúsculas são ignoradas |
Orbit, 40 × 20 e orbit, 41 × 20 |
Diferentes | A largura participa da igualdade |
Orbit, 40 × 20 e orbit, 40 × 21 |
Diferentes | A altura participa da igualdade |
Orbit, 40 × 20 e orbit , 40 × 20 |
Diferentes | O espaço final é preservado |
Use uma chave dedicada quando uma regra específica de agrupamento diferir do valor completo de um bloco exibido. Preserve a grafia original para exibição no modelo de apresentação ao redor. Assim, você evita declarar intercambiáveis duas descrições completas de blocos cujos nomes visíveis são diferentes. A documentação de Equatable do Swift descreve a igualdade em termos de substituibilidade e exige reflexividade, simetria e transitividade. Equatable
Use os mesmos componentes na igualdade e no hashing
O protocolo Hashable do Swift herda de Equatable. Seu requisito essencial é que instâncias iguais forneçam os mesmos valores ao hasher, na mesma ordem. Valores Hashable podem ser usados como elementos de conjuntos e chaves de dicionários. Hashable
Um erro frequente é comparar nomes normalizados em ==, mas calcular o hash usando a grafia original. Assim, os nomes têm significados diferentes nos dois métodos. Outro erro é omitir a largura da igualdade e incluí-la no hashing. Ambos os projetos permitem que valores declarados iguais forneçam entradas diferentes ao hashing. A Apple recomenda explicitamente que implementações personalizadas usem as mesmas propriedades significativas nos dois métodos. Adopting Common Protocols
Neste exemplo, coloque os componentes da igualdade em uma estrutura aninhada privada. Deixe o Swift sintetizar a igualdade e o hashing dessa estrutura e, em seguida, delegue a ela as duas operações externas. Assim, a lista de componentes fica em uma única declaração que você pode inspecionar. O Swift oferece suporte à conformidade sintetizada para estruturas cujas propriedades armazenadas estejam em conformidade com os protocolos necessários. Protocols — Adopting a Protocol Using a Synthesized Implementation
Se seu tipo já armazena exatamente os componentes da igualdade, geralmente basta sintetizar a conformidade com Hashable. Os métodos externos explícitos abaixo mostram como manter uma implementação personalizada existente alinhada, sem manter duas listas de campos independentes.
Execute o exemplo local completo no Mac
Você precisa de uma cadeia de ferramentas Swift instalada com Foundation. No Terminal, confira com swift --version, conforme recomendado na documentação de introdução ao Swift. Se o Swift não estiver disponível, siga o guia oficial de instalação para macOS antes de continuar. Getting Started, Install Swift — macOS
A seguir há um comando completo do shell que contém o programa Swift. Cole o bloco inteiro no Terminal. Ele fornece o programa pela entrada padrão; o exemplo faz apenas cálculos na memória e imprime os resultados. O código Swift foi inspecionado, mas não pôde ser executado no ambiente de pesquisa porque o Swift não estava disponível.
swift - <<'SWIFT'
import Foundation
struct TileKey: Hashable, Sendable {
private struct Components: Hashable, Sendable {
let foldedName: String
let width: Int
let height: Int
}
private let components: Components
init(name: String, width: Int, height: Int) {
precondition(width > 0 && height > 0,
"Tile dimensions must be positive")
let foldedName = name.folding(
options: [.caseInsensitive],
locale: Locale(identifier: "en_US_POSIX")
)
components = Components(
foldedName: foldedName,
width: width,
height: height
)
}
static func == (lhs: TileKey, rhs: TileKey) -> Bool {
lhs.components == rhs.components
}
func hash(into hasher: inout Hasher) {
hasher.combine(components)
}
}
let a = TileKey(name: "Orbit", width: 40, height: 20)
let b = TileKey(name: "ORBIT", width: 40, height: 20)
let c = TileKey(name: "orbit", width: 40, height: 20)
let wider = TileKey(name: "orbit", width: 41, height: 20)
let taller = TileKey(name: "orbit", width: 40, height: 21)
let spaced = TileKey(name: "orbit ", width: 40, height: 20)
let samples = [a, b, c, wider, taller, spaced]
precondition(a == b && b == c && a == c)
precondition(a != wider && a != taller && a != spaced)
// Check the equality laws over this finite sample.
for first in samples {
precondition(first == first, "Reflexivity failed")
for second in samples {
precondition((first == second) == (second == first),
"Symmetry failed")
if first == second {
precondition(first.hashValue == second.hashValue,
"Equal values have different hashes")
}
for third in samples where first == second && second == third {
precondition(first == third, "Transitivity failed")
}
}
}
let uniqueKeys = Set(samples)
precondition(uniqueKeys.count == 4)
precondition(uniqueKeys.contains(b))
let patterns = [a: "striped"]
precondition(patterns[b] == "striped")
print("Unique keys:", uniqueKeys.count)
print("Equivalent key found:", uniqueKeys.contains(b))
print("Pattern:", patterns[b] ?? "missing")
print("Sample contract checks passed")
SWIFT
Saída esperada:
Unique keys: 4
Equivalent key found: true
Pattern: striped
Sample contract checks passed
As seis entradas formam quatro grupos: as três variantes de maiúsculas e minúsculas compartilham uma chave, enquanto as variantes com largura alterada, altura alterada e espaço final formam, cada uma, outro grupo. A verificação do dicionário pergunta se um valor associado a a pode ser recuperado usando a chave igual b. Essas verificações exercitam o comportamento pretendido das coleções sem depender de nenhum valor de hash específico.
O inicializador interrompe a execução deliberadamente quando as dimensões não são positivas. Isso é apropriado para este exercício sintético fixo, no qual dimensões inválidas indicam um erro de programação. Se uma interface de desktop permitir a entrada incompleta de dimensões, valide os dados antes de construir a chave ou altere o inicializador para retornar um opcional ou lançar um erro. Não substitua silenciosamente uma dimensão, pois isso altera o contrato de igualdade.
Torne explícita a regra de normalização do texto
A etapa de normalização chama folding(options:locale:) de Foundation por meio da API de strings do Swift. A API retorna uma string com as opções de conversão especificadas aplicadas. Aqui, apenas .caseInsensitive é solicitado. StringProtocol.folding(options:locale:)
A documentação de Foundation descreve a conversão de maiúsculas e minúsculas, largura e diacríticos como opções separadas. Também explica que a localidade pode alterar os resultados da conversão e que o texto convertido pode não ser adequado para exibição. Por isso, o exemplo fornece uma localidade fixa e armazena o resultado convertido apenas dentro da chave. Os nomes sintéticos são deliberadamente simples. Essa escolha de localidade define o comportamento deste exercício; não é uma recomendação universal para toda interface de nomenclatura. NSString.folding(options:locale:)
A decisão útil de projeto é normalizar uma vez durante a construção. A igualdade e o hashing usam depois o resultado armazenado, então nenhuma das operações escolhe uma localidade ou transformação por conta própria. Se mais tarde a aplicação precisar de outra política de comparação, construa chaves segundo essa nova política e recrie a coleção em memória afetada.
Antes de ampliar a normalização, acrescente exemplos que distingam os grupos desejados. Os espaços devem ser relevantes? Letras acentuadas devem continuar diferentes? Larguras de caracteres visualmente distintas devem contar como o mesmo nome? Responda a essas perguntas como regras do produto e, então, implemente uma transformação usada pelas duas operações.
Diagnostique uma incompatibilidade sem confiar nos valores de hash
Use a sequência a seguir ao adaptar este projeto a um tipo existente:
- Escreva um par que deva ser considerado igual e um par para cada campo que deva distinguir valores.
- Rastreie os valores realmente comparados por
==, incluindo normalização e propriedades derivadas. - Rastreie os valores fornecidos a
hasher.combine, incluindo a ordem. - Coloque a definição compartilhada em um único valor de componente imutável ou faça ambas as implementações usarem explicitamente os mesmos componentes.
- Verifique as leis da igualdade, os hashes de valores iguais, a associação a conjuntos e a busca em dicionários usando os mesmos dados de teste.
Não implemente a igualdade comparando hashValue. Valores iguais precisam ter hashes iguais, mas hashes correspondentes não estabelecem igualdade. Os componentes completos da igualdade continuam sendo a base de ==. Da mesma forma, um teste que exija que todo par de valores diferentes tenha hashes diferentes exige mais do que o contrato de hashing garante. Hashable
Compare hashes apenas dentro da execução atual ao verificar esse contrato. O Hasher do Swift usa uma semente aleatória, e seu algoritmo pode mudar entre versões da biblioteca padrão. Portanto, um número impresso ontem não serve como resultado esperado para a execução de hoje. Hasher
As verificações com amostras finitas são úteis como regressões, mas não constituem uma prova para toda string e dimensão possível. O argumento estrutural também importa: os dois métodos externos delegam ao mesmo valor imutável Components, cujos campos armazenados expressam integralmente o contrato escolhido.
Mantenha os requisitos de concorrência separados da igualdade
O exemplo declara ambas as estruturas como Sendable porque seus valores armazenados são strings, inteiros e outra estrutura enviável. O Swift documenta que estruturas enviáveis precisam conter propriedades armazenadas enviáveis. A capacidade de envio diz respeito à transferência de valores entre domínios de concorrência; ela não define quais valores são considerados iguais. Sendable, The Swift Programming Language — Concurrency
Este cálculo local não precisa de tarefas nem atores. Se mais tarde fizer parte de código concorrente para desktop, mantenha a igualdade e o hashing como operações síncronas sobre os componentes armazenados da chave. Trate qualquer coleção mutável compartilhada como uma preocupação de projeto de concorrência separada. O guia da linguagem descreve atores e isolamento como mecanismos para coordenar o acesso a estado mutável. The Swift Programming Language — Concurrency
Também existe uma particularidade de Foundation ao adaptar o padrão: uma subclasse de NSObject usa o comportamento herdado de igualdade e hashing. O caminho de personalização compatível da Apple é substituir isEqual(_:) e hash em conjunto. A estrutura Swift simples deste exemplo usa == e hash(into:). Adopting Common Protocols
Para uma chave local de bloco, o resultado sustentável é uma regra de comparação documentada, uma representação imutável dessa regra e verificações que exercitam buscas reais em coleções. Quando a definição mudar, atualize juntos a representação dos componentes e seus exemplos para que a igualdade e o hashing continuem descrevendo o mesmo valor.
Fontes
- Hashable — Apple
- Equatable — Apple
- Hasher — Apple
- Adopting Common Protocols — Apple
- folding(options:locale:) — StringProtocol — Apple
- folding(options:locale:) — NSString — Apple
- Sendable — Apple
- Protocols — The Swift Programming Language, Swift.org
- Concurrency — The Swift Programming Language, Swift.org
- Getting Started — Swift.org
- Install Swift — macOS — Swift.org