Faites reposer l’égalité et le hachage personnalisés en Swift sur une seule définition de valeur immuable. Ce guide crée une clé de tuile locale dont le nom ignore la casse tandis que ses dimensions restent exactes, puis vérifie l’égalité, l’appartenance à un ensemble et la recherche dans un dictionnaire.
Définissez précisément quand deux tuiles sont identiques
Supposons que vous prépariez un petit exercice graphique pour ordinateur de bureau Mac. Vous devez regrouper des descriptions synthétiques de tuiles selon leur nom, leur largeur et leur hauteur. La tâche consiste à implémenter ce regroupement de façon cohérente : Orbit et ORBIT de mêmes dimensions doivent produire des clés égales ; modifier l’une ou l’autre dimension doit produire une clé différente.
Énoncez le contrat avant d’implémenter l’un ou l’autre protocole. Dans cet exemple, deux clés sont égales lorsque leurs noms convertis sans tenir compte de la casse, leurs largeurs et leurs hauteurs sont égaux. Les dimensions sont des unités de dessin entières positives. Les espaces de début et de fin restent significatifs. Il s’agit d’une règle d’application illustrative, et non d’une exigence imposée par Swift.
| Entrées synthétiques | Relation attendue | Raison |
|---|---|---|
Orbit, 40 × 20 et ORBIT, 40 × 20 |
Égales | La casse des lettres est ignorée |
Orbit, 40 × 20 et orbit, 41 × 20 |
Inégales | La largeur participe à l’égalité |
Orbit, 40 × 20 et orbit, 40 × 21 |
Inégales | La hauteur participe à l’égalité |
Orbit, 40 × 20 et orbit , 40 × 20 |
Inégales | L’espace final est conservé |
Utilisez une clé dédiée lorsque la règle de regroupement diffère de la valeur complète d’une tuile affichée. Conservez l’orthographe d’origine pour l’affichage dans le modèle de présentation environnant. Vous éviterez ainsi de déclarer interchangeables deux descriptions complètes de tuiles dont les noms visiblement différents font partie de leur valeur. La documentation Equatable de Swift décrit l’égalité en termes de substituabilité et exige la réflexivité, la symétrie et la transitivité. Equatable
Utilisez les mêmes composants pour l’égalité et le hachage
Le protocole Hashable de Swift hérite de Equatable. Son exigence essentielle est que des instances égales fournissent au hacheur les mêmes valeurs dans le même ordre. Les valeurs Hashable peuvent servir d’éléments d’ensemble et de clés de dictionnaire. Hashable
Une erreur fréquente consiste à comparer les noms normalisés dans == tout en hachant leur orthographe d’origine. Les noms ont alors un sens différent dans les deux méthodes. Une autre erreur consiste à omettre la largeur de l’égalité tout en l’incluant dans le hachage. Dans les deux cas, des valeurs déclarées égales peuvent fournir des données de hachage différentes. Apple précise que les implémentations personnalisées doivent utiliser les mêmes propriétés significatives dans les deux méthodes. Adopting Common Protocols
Dans cet exemple, placez les composants de l’égalité dans une structure imbriquée privée. Laissez Swift synthétiser l’égalité et le hachage de cette structure, puis déléguez-lui les deux opérations de la structure externe. La liste des composants est ainsi déclarée en un seul endroit où vous pouvez la vérifier. Swift prend en charge la conformité synthétisée pour les structures dont les propriétés stockées sont conformes aux protocoles requis. Protocols — Adopting a Protocol Using a Synthesized Implementation
Si votre type stocke déjà exactement les composants de l’égalité, la conformité Hashable synthétisée suffit généralement. Les méthodes externes explicites ci-dessous montrent comment garder une implémentation personnalisée existante cohérente sans maintenir deux listes de champs indépendantes.
Exécutez l’exemple local complet sur votre Mac
Vous avez besoin d’une chaîne d’outils Swift installée avec Foundation. Dans Terminal, vérifiez-la avec swift --version, comme le recommande la documentation de prise en main de Swift. Si Swift n’est pas disponible, suivez le guide officiel d’installation pour macOS avant de continuer. Getting Started, Install Swift — macOS
Le bloc suivant est une commande shell complète contenant le programme Swift. Collez le bloc entier dans Terminal. Le programme est transmis par l’entrée standard ; l’exemple effectue uniquement des calculs en mémoire et affiche les résultats. Le code Swift a été examiné, mais n’a pas pu être exécuté dans l’environnement de recherche, car Swift n’y était pas disponible.
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
Résultat attendu :
Unique keys: 4
Equivalent key found: true
Pattern: striped
Sample contract checks passed
Les six entrées forment quatre groupes : les trois variantes de casse partagent une clé, tandis que la largeur modifiée, la hauteur modifiée et la variante avec espace final forment chacune un groupe distinct. La vérification du dictionnaire demande si une valeur associée à a peut être récupérée à l’aide de la clé égale b. Ces vérifications exercent le comportement attendu des collections sans dépendre d’une valeur de hachage particulière.
L’initialiseur s’interrompt délibérément si les dimensions ne sont pas positives. C’est approprié pour cet exercice synthétique fixe, où des dimensions non valides indiquent une erreur de programmation. Si une interface de bureau autorise la saisie de dimensions incomplète, validez ces données avant de construire la clé, ou modifiez l’initialiseur pour qu’il renvoie une valeur facultative ou lance une erreur. Ne remplacez pas silencieusement une dimension, car cela modifie le contrat d’égalité.
Faites de la normalisation du texte une règle explicite
L’étape de normalisation appelle folding(options:locale:) de Foundation par l’intermédiaire de l’API de chaînes de Swift. Cette API renvoie une chaîne à laquelle les options de conversion indiquées ont été appliquées. Ici, seule l’option .caseInsensitive est demandée. StringProtocol.folding(options:locale:)
La documentation de Foundation présente séparément la conversion de la casse, de la largeur et des signes diacritiques. Elle précise également que la locale peut modifier les résultats et que le texte converti peut ne pas convenir à l’affichage. L’exemple fournit donc une locale fixe et ne stocke le résultat converti qu’à l’intérieur de la clé. Les noms synthétiques sont volontairement simples. Ce choix de locale définit le comportement de cet exercice ; ce n’est pas une recommandation universelle pour toutes les interfaces de nommage. NSString.folding(options:locale:)
La décision de conception utile consiste à normaliser une seule fois lors de la construction. L’égalité et le hachage utilisent ensuite le résultat stocké ; aucune des deux opérations ne choisit donc indépendamment une locale ou une transformation. Si l’application nécessite ensuite une autre règle de comparaison, construisez les clés selon cette nouvelle règle et recréez la collection en mémoire concernée.
Avant d’élargir la normalisation, ajoutez des exemples qui distinguent les groupes souhaités. Les espaces doivent-ils compter ? Les lettres accentuées doivent-elles rester distinctes ? Des largeurs de caractères visuellement différentes doivent-elles compter comme un même nom ? Répondez à ces questions en définissant les règles du produit, puis implémentez une transformation unique utilisée par les deux opérations.
Diagnostiquez une incohérence sans vous fier aux valeurs de hachage
Suivez cette séquence pour adapter cette conception à un type existant :
- Écrivez une paire qui doit être comparée comme égale et une paire pour chaque champ censé distinguer les valeurs.
- Suivez les valeurs réellement comparées par
==, y compris la normalisation et les propriétés dérivées. - Suivez les valeurs fournies à
hasher.combine, y compris leur ordre. - Placez la définition commune dans une valeur de composant immuable, ou faites en sorte que les deux implémentations utilisent explicitement les mêmes composants.
- Vérifiez les lois de l’égalité, les hachages des valeurs égales, l’appartenance à un ensemble et la recherche dans un dictionnaire avec les mêmes données de test.
N’implémentez pas l’égalité en comparant hashValue. Les valeurs égales doivent avoir des hachages égaux, mais un hachage identique ne prouve pas l’égalité. Les composants complets de l’égalité restent la base de ==. De même, un test qui exige que chaque paire inégale ait des hachages différents impose davantage que ce que garantit le contrat de hachage. Hashable
Comparez les hachages uniquement au cours de l’exécution en cours lorsque vous vérifiez ce contrat. Le Hasher de Swift utilise une initialisation aléatoire, et son algorithme peut changer d’une version de la bibliothèque standard à l’autre. Un entier affiché lors de l’exécution d’hier ne convient donc pas comme résultat attendu pour celle d’aujourd’hui. Hasher
Les vérifications sur un échantillon fini sont utiles pour les tests de régression, mais elles ne constituent pas une preuve pour toutes les chaînes et toutes les dimensions possibles. L’argument structurel compte aussi : les deux méthodes externes délèguent à la même valeur Components immuable, dont les champs stockés expriment entièrement le contrat choisi.
Distinguez les exigences de concurrence de l’égalité
L’exemple déclare les deux structures Sendable parce que leurs valeurs stockées sont des chaînes, des entiers et une autre structure transférable. Swift précise que les structures transférables doivent contenir des propriétés stockées transférables. La transférabilité concerne le transfert de valeurs entre domaines de concurrence ; elle ne définit pas quelles valeurs sont considérées comme égales. Sendable, The Swift Programming Language — Concurrency
Ce calcul local ne nécessite ni tâches ni acteurs. S’il fait ensuite partie d’un code de bureau concurrent, faites en sorte que l’égalité et le hachage restent des opérations synchrones portant sur les composants stockés de la clé. Traitez toute collection partagée modifiable comme un problème de conception de la concurrence distinct. Le guide du langage décrit les acteurs et l’isolation comme des mécanismes de coordination de l’accès à un état mutable. The Swift Programming Language — Concurrency
Il existe également une limite liée à Foundation lors de l’adaptation de cette approche : une sous-classe de NSObject utilise les comportements hérités d’égalité et de hachage. La méthode de personnalisation prise en charge par Apple consiste à redéfinir ensemble isEqual(_:) et hash. La structure Swift simple de cet exemple utilise == et hash(into:). Adopting Common Protocols
Pour une clé de tuile locale, le résultat facile à maintenir repose sur une règle de comparaison écrite, une représentation immuable de cette règle et des vérifications qui exercent de véritables recherches dans les collections. Lorsque la définition change, mettez à jour ensemble la représentation des composants et leurs exemples afin que l’égalité et le hachage continuent de décrire la même valeur.
Sources
- 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