Pour fusionner deux valeurs de structure Swift, écrivez une fonction qui choisit la source de chaque champ et renvoie une nouvelle valeur. Cet exemple pour macOS combine deux styles synthétiques de panneau de dessin, avec des règles explicites de remplacement, de conservation, d’effacement d’une valeur facultative et de rejet des entrées invalides.

Définir ce qui l’emporte avant d’écrire la fusion

La tâche du lecteur consiste à calculer un style de panneau de dessin à partir de deux valeurs existantes sur un Mac. Appelez les entrées base et incoming : ces noms décrivent leurs rôles, tandis qu’un contrat champ par champ détermine quelle valeur l’emporte. Le calcul utilise uniquement des valeurs synthétiques en mémoire.

Swift décrit les structures comme des types valeur et permet de construire des instances à l’aide d’initialiseurs. L’API documentée par Apple, merge(_:uniquingKeysWith:), appartient à Dictionary ; elle résout les clés en double au moyen d’une fermeture. Pour cette structure personnalisée, définissez une fonction de fusion dont l’implémentation exprime les règles voulues pour chaque champ. Structures et classes, Dictionary merge(_:uniquingKeysWith:).

Utilisez le contrat illustratif suivant. Il s’agit de choix de conception propres à cet exemple, et non d’un comportement fourni automatiquement par Swift.

Champ Règle Conséquence de l’exemple
title Prendre le texte entrant, supprimer les espaces et retours à la ligne qui l’entourent, puis rejeter un résultat vide " Tile Study \n" devient \"Tile Study\"
spacing Conserver la valeur de base ; exiger qu’elle soit non négative La valeur de base 12 l’emporte sur la valeur entrante 30
showsGrid Prendre exactement la valeur booléenne entrante La valeur entrante false remplace la valeur de base true
caption Prendre exactement la valeur facultative entrante La valeur entrante nil efface la légende de base

Ce tableau distingue trois opérations faciles à confondre : choisir une source, normaliser la valeur choisie et valider le résultat. L’espacement entrant est volontairement ignoré. Il ne peut pas modifier le résultat, même si sa valeur diffère de celle de l’espacement de base.

La méthode Foundation trimmingCharacters(in:) renvoie une nouvelle chaîne après avoir supprimé les caractères correspondants à ses deux extrémités. Apple documente l’utilisation de .whitespacesAndNewlines pour les espaces périphériques ; une chaîne composée uniquement de ces caractères devient vide. Le rejet de ce résultat vide est une règle supplémentaire que nous ajoutons. Foundation trimmingCharacters(in:).

Exécuter un exemple local complet sur votre Mac

Utilisez une chaîne d’outils Swift 6 pour cet exemple. Swift.org fournit des instructions d’installation pour macOS avec Swiftly et indique que Xcode est une option pour le développement sur les plateformes Apple. Ouvrez Terminal et exécutez swift --version pour vérifier que la commande est disponible. Si elle ne l’est pas, suivez les instructions d’installation avant de continuer. Installer Swift sur macOS, Premiers pas.

Enregistrez le programme complet suivant sous le nom MergeStyles.swift dans un dossier de travail. Il importe Foundation, définit les deux valeurs d’entrée, effectue la fusion, vérifie le résultat attendu et teste les deux cas de rejet. Le calcul n’effectue aucune requête réseau et ne stocke aucune donnée d’application.

import Foundation

struct PanelStyle: Equatable, Sendable {
    let title: String
    let spacing: Int
    let showsGrid: Bool
    let caption: String?
}

enum StyleMergeError: Error {
    case emptyTitle
    case negativeSpacing
}

func mergeStyles(
    base: PanelStyle,
    incoming: PanelStyle
) throws -> PanelStyle {
    let title = incoming.title.trimmingCharacters(
        in: .whitespacesAndNewlines
    )
    guard !title.isEmpty else {
        throw StyleMergeError.emptyTitle
    }
    guard base.spacing >= 0 else {
        throw StyleMergeError.negativeSpacing
    }

    return PanelStyle(
        title: title,
        spacing: base.spacing,
        showsGrid: incoming.showsGrid,
        caption: incoming.caption
    )
}

let base = PanelStyle(
    title: "Shape Study",
    spacing: 12,
    showsGrid: true,
    caption: "Blue squares"
)
let incoming = PanelStyle(
    title: "  Tile Study \n",
    spacing: 30,
    showsGrid: false,
    caption: nil
)

let expected = PanelStyle(
    title: "Tile Study",
    spacing: 12,
    showsGrid: false,
    caption: nil
)

let result = try mergeStyles(base: base, incoming: incoming)
precondition(result == expected)
precondition(base.title == "Shape Study")
precondition(base.caption == "Blue squares")
precondition(incoming.title == "  Tile Study \n")

print(result.title)
print("spacing: \(result.spacing)")
print("grid: \(result.showsGrid)")
print("caption cleared: \(result.caption == nil)")

let blankTitle = PanelStyle(
    title: " \n\t",
    spacing: 30,
    showsGrid: false,
    caption: nil
)
do {
    _ = try mergeStyles(base: base, incoming: blankTitle)
    preconditionFailure("Expected emptyTitle")
} catch StyleMergeError.emptyTitle {
    print("Rejected blank title")
}

let negativeBase = PanelStyle(
    title: "Shape Study",
    spacing: -1,
    showsGrid: true,
    caption: "Blue squares"
)
do {
    _ = try mergeStyles(base: negativeBase, incoming: incoming)
    preconditionFailure("Expected negativeSpacing")
} catch StyleMergeError.negativeSpacing {
    print("Rejected negative spacing")
}

Depuis le dossier contenant le fichier, exécutez :

swift MergeStyles.swift

Le résultat attendu est indiqué ci-dessous. Swift n’était pas disponible dans l’environnement de recherche ; le programme y a donc été examiné, mais pas exécuté.

Tile Study
espacement : 12
grille : false
légende effacée : true
Titre vide rejeté
Espacement négatif rejeté

L’initialiseur est l’aide à la décision centrale : chaque argument désigne soit le champ d’entrée choisi, soit une valeur intermédiaire nommée. Aucune des deux entrées n’est modifiée. La sémantique des valeurs de Swift convient à cette approche ; les champs utilisés ici sont eux-mêmes des types valeur. Si vous ajoutez ensuite un champ de type classe, la copie de la structure qui le contient ne copie pas indépendamment l’instance de classe référencée. Structures et classes.

La validation a lieu avant la construction. Un titre entrant vide déclenche une erreur au lieu de rétablir silencieusement le titre de base, et un espacement de base négatif déclenche une erreur au lieu d’être ramené silencieusement à zéro. Si les deux conditions échouent, emptyTitle est signalé en premier, car cette garde est exécutée en premier. Les appelants doivent traiter l’erreur avant d’utiliser un résultat.

Décider si une valeur facultative signifie effacer ou conserver

L’exemple principal considère incoming.caption comme la légende souhaitée dans son intégralité. Sa valeur nil signifie donc « le résultat n’a pas de légende ». Le contrat rend ainsi l’effacement explicite, même si la représentation est simplement une valeur facultative.

Une autre exigence pourrait préciser que nil entrant signifie « conserver la légende de base ». Avec cette règle, l’argument caption de l’initialiseur de remplacement serait caption: incoming.caption ?? base.caption. L’opérateur de coalescence des valeurs nil de Swift choisit son opérande gauche s’il contient une valeur, et utilise sinon l’opérande droit. Il s’agit d’un fragment de remplacement étiqueté, et non d’un second programme complet. Opérateur de coalescence des valeurs nil.

Cette règle de repli ne peut pas également utiliser la même valeur nil entrante pour demander l’effacement. Choisissez la signification avant d’adopter cette expression. Une chaîne vide est une autre valeur distincte : quelle que soit la règle, une légende entrante égale à \"\" reste une légende vide, sauf si vous ajoutez explicitement une normalisation.

Lorsqu’une future opération d’édition devra prendre en charge les trois actions — conserver, remplacer et effacer — représentez l’action séparément. Le programme autonome complet suivant illustre une opération limitée à la légende, au moyen d’une énumération personnalisée :

enum CaptionChange {
    case keep
    case set(String?)
}

func applying(
    _ change: CaptionChange,
    to caption: String?
) -> String? {
    switch change {
    case .keep:
        return caption
    case .set(let replacement):
        return replacement
    }
}

let original: String? = "Blue squares"
precondition(applying(.keep, to: original) == original)
precondition(applying(.set("Green circles"), to: original)
    == "Green circles")
precondition(applying(.set(nil), to: original) == nil)
print("Caption actions checked")

Cette énumération illustre une extension du contrat. Conservez la fonction d’origine à deux valeurs lorsque les deux entrées sont des styles complets ; introduisez un type d’action lorsque l’entrée décrit réellement des modifications.

Vérifier la priorité avec des valeurs volontairement différentes

Pour vérifier utilement une fusion, les champs en conflit doivent avoir des valeurs différentes. Si les deux entrées ont un espacement de 12 et activent toutes deux la grille, un choix incorrect de source peut passer inaperçu. L’exemple utilise plutôt 12 contre 30, et true contre false, afin que le résultat attendu révèle ces choix.

Avant d’adapter la fonction, vérifiez les cas limites suivants par rapport au contrat souhaité :

  • La valeur entrante false doit rester false ; n’utilisez pas un OU booléen pour exprimer le remplacement.
  • La légende entrante nil doit effacer la légende selon la règle principale.
  • Les espaces autour du titre doivent être supprimés, tandis que les espaces internes doivent être conservés.
  • Un titre entrant composé uniquement d’espaces doit déclencher une erreur, sans repli sur le titre de base.
  • L’espacement de base 0 doit être accepté ; un espacement de base négatif doit déclencher une erreur.
  • L’inversion des arguments doit appliquer les règles aux nouveaux rôles attribués.

Ce dernier point est important, car cette fusion dépend de l’ordre des arguments. En inversant les entrées de l’exemple, on obtient \"Shape Study\", un espacement de 30, une grille true et une légende \"Blue squares\". C’est le résultat des règles énoncées. Nommez les deux arguments aux points d’appel afin que leurs rôles restent visibles.

Lorsque vous ajoutez un champ, complétez le tableau, l’initialiseur et une vérification utilisant des valeurs différentes. Si un futur style imbriqué contient plusieurs paramètres indépendants, décidez si toute la valeur imbriquée est remplacée ou si chacun de ses champs possède sa propre priorité. L’implémentation actuelle ne définit que les quatre champs indiqués.

Garder la concurrence séparée de la règle de fusion

Ce calcul est synchrone : il lit ses arguments, valide les valeurs sélectionnées et renvoie un résultat ou déclenche une erreur. Il n’a besoin ni d’une tâche ni d’un acteur pour établir une priorité. Swift décrit les fonctions asynchrones comme des fonctions qui peuvent être suspendues ; l’ajout d’une exécution asynchrone ne déciderait pas quel champ l’emporte. Concurrence.

L’exemple déclare PanelStyle comme Sendable pour indiquer que ses valeurs peuvent franchir les frontières de concurrence. Apple exige qu’une structure conforme à Sendable ne contienne que des membres conformes à Sendable. Cette conformité concerne le transfert des valeurs ; les règles de sélection des champs proviennent entièrement de mergeStyles. Sendable.

Pour cette tâche locale de panneau de dessin, gardez le contrat près de la fonction et utilisez des entrées d’exemple visiblement différentes. Le résultat doit pouvoir s’expliquer champ par champ : un titre entrant normalisé, l’espacement de base conservé, le paramètre de grille entrant et une légende entrante qui peut effacer explicitement la valeur précédente.

Sources