Per unire due valori di struct Swift, scrivi una funzione che scelga la sorgente per ogni campo e restituisca un nuovo valore. Questo esempio per macOS combina due stili sintetici di pannello di disegno, con regole esplicite per la sostituzione, la conservazione, l’azzeramento di valori opzionali e gli input non validi.

Definisci cosa prevale prima di scrivere la funzione di unione

Il compito del lettore è calcolare uno stile di pannello di disegno a partire da due valori esistenti su un Mac. Chiama gli input base e incoming: questi nomi ne descrivono i ruoli, mentre un contratto campo per campo determina quale valore prevale. Il calcolo usa solo valori sintetici in memoria.

Swift descrive le strutture come tipi valore e permette di creare istanze tramite inizializzatori. L’API merge(_:uniquingKeysWith:) documentata da Apple appartiene a Dictionary; risolve le chiavi duplicate tramite una closure. Per questa struct personalizzata, definisci una funzione di unione la cui implementazione esprima le regole desiderate per i campi. Strutture e classi, Dictionary merge(_:uniquingKeysWith:).

Usa il seguente contratto esemplificativo. Si tratta di scelte progettuali per questo esempio, non di comportamenti forniti automaticamente da Swift.

Campo Regola Conseguenza di esempio
title Usa il testo in ingresso, rimuovi gli spazi e i caratteri di nuova riga alle estremità, poi rifiuta un risultato vuoto " Tile Study \n" diventa \"Tile Study\"
spacing Conserva il valore di base; deve essere non negativo Il valore di base 12 prevale su quello in ingresso 30
showsGrid Usa esattamente il valore booleano in ingresso Il valore in ingresso false sostituisce quello di base true
caption Usa esattamente l’opzionale in ingresso Il valore in ingresso nil azzera la didascalia di base

Questa tabella distingue tre operazioni che è facile confondere: selezionare una sorgente, normalizzare il valore selezionato e convalidare il risultato. Gli spazi del valore in ingresso sono intenzionalmente ignorati. Non possono modificare l’output, anche se il loro valore differisce da quello degli spazi di base.

Foundation’s trimmingCharacters(in:) restituisce una nuova stringa rimuovendo i caratteri corrispondenti da entrambe le estremità. Apple documenta l’uso di .whitespacesAndNewlines per gli spazi alle estremità; una stringa composta interamente da questi caratteri diventa vuota. Rifiutare il risultato vuoto è una regola aggiuntiva definita da noi. Foundation trimmingCharacters(in:).

Esegui un esempio locale completo sul tuo Mac

Per questo esempio usa una toolchain Swift 6. Swift.org fornisce istruzioni per l’installazione su macOS tramite Swiftly e indica Xcode come opzione per lo sviluppo sulle piattaforme Apple. Apri Terminal ed esegui swift --version per verificare che il comando sia disponibile. Se non lo è, segui le istruzioni di installazione prima di continuare. Installare Swift su macOS, Guida introduttiva.

Salva il seguente programma completo in una cartella di lavoro con il nome MergeStyles.swift. Importa Foundation, definisce entrambi i valori di input, esegue l’unione, verifica il risultato previsto e prova i due casi di rifiuto. Il calcolo non effettua richieste di rete né memorizza dati dell’applicazione.

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")
}

Dalla cartella che contiene il file, esegui:

swift MergeStyles.swift

L’output previsto è mostrato di seguito. Swift non era disponibile nell’ambiente di ricerca, quindi il programma è stato esaminato ma non eseguito in quell’ambiente.

Tile Study
spacing: 12
grid: false
caption cleared: true
Rejected blank title
Rejected negative spacing

L’inizializzatore è il punto centrale per le decisioni: ogni argomento identifica il campo di input selezionato o un valore intermedio con un nome esplicito. Nessuno dei due input viene modificato. La semantica dei valori di Swift supporta questo approccio; in questo caso anche i campi sono tipi valore. Se in seguito aggiungi un campo di tipo class, la copia della struct contenitore non crea una copia indipendente dell’istanza class referenziata. Strutture e classi.

La convalida avviene prima della creazione. Un titolo in ingresso vuoto genera un errore invece di ripristinare silenziosamente il titolo di base; uno spazio di base negativo genera un errore invece di essere portato silenziosamente a zero. Se entrambe le condizioni non sono soddisfatte, viene segnalato prima emptyTitle, perché il relativo controllo è il primo. I chiamanti devono gestire l’errore prima di usare un risultato.

Decidi se un opzionale significa azzerare o conservare

L’esempio principale considera incoming.caption come la didascalia desiderata completa. Pertanto, nil significa «il risultato non ha didascalia». In questo modo l’azzeramento è esplicito nel contratto, anche se la rappresentazione è semplicemente un opzionale.

Un requisito diverso potrebbe specificare che nil in ingresso significa «mantieni la didascalia di base». Con questa regola, l’argomento caption dell’inizializzatore di sostituzione sarebbe caption: incoming.caption ?? base.caption. L’operatore nil-coalescing di Swift seleziona l’operando a sinistra quando contiene un valore, altrimenti usa quello a destra. È un frammento di sostituzione etichettato, non un secondo programma completo. Operatore nil-coalescing.

Questa regola di fallback non può usare anche lo stesso nil in ingresso per richiedere l’azzeramento. Decidi il significato prima di adottare l’espressione. Anche una stringa vuota è un valore distinto: con entrambe le regole, una didascalia in ingresso pari a \"\" rimane una didascalia vuota, a meno che tu non aggiunga esplicitamente una normalizzazione.

Quando una futura operazione di modifica richiede tutte e tre le azioni — conservare, sostituire e azzerare — rappresenta l’azione separatamente. Il seguente programma autonomo e completo illustra un’operazione che riguarda solo la didascalia, usando un enum personalizzato:

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")

Questo enum è un’estensione esemplificativa del contratto. Mantieni la funzione originale a due valori quando entrambi gli input sono stili completi; introduci un tipo azione quando l’input descrive effettivamente delle modifiche.

Verifica la precedenza usando valori deliberatamente diversi

Una verifica utile della funzione di unione fa sì che i campi in competizione siano diversi. Se entrambi gli input hanno spaziatura 12 ed entrambi attivano la griglia, una scelta errata della sorgente potrebbe passare inosservata. L’esempio usa invece 12 rispetto a 30 e true rispetto a false, così il risultato previsto rende evidenti queste scelte.

Prima di adattare la funzione, verifica questi casi limite rispetto al contratto desiderato:

  • false in ingresso deve rimanere false; non usare l’operatore OR booleano per esprimere una sostituzione.
  • La didascalia in ingresso nil deve azzerare la didascalia secondo la regola principale.
  • Gli spazi all’inizio e alla fine del titolo devono essere rimossi, mentre quelli interni restano.
  • Un titolo in ingresso composto solo da spazi deve generare un errore, senza ripiegare sul valore di base.
  • Lo spazio di base 0 deve essere valido; uno spazio di base negativo deve generare un errore.
  • Invertendo gli argomenti, le regole devono applicarsi ai ruoli appena assegnati.

L’ultimo punto è importante perché questa unione dipende dall’ordine. Invertendo gli input dell’esempio, il risultato è \"Shape Study\", spaziatura 30, griglia true e didascalia \"Blue squares\". È il risultato delle regole dichiarate. Dai un nome a entrambi gli argomenti nei punti di chiamata, così i loro ruoli restano visibili.

Quando aggiungi un campo, estendi la tabella, l’inizializzatore e una verifica con valori in competizione. Se uno stile annidato futuro contiene diverse impostazioni indipendenti, decidi se sostituire l’intero valore annidato oppure se ogni suo campo ha una propria precedenza. L’implementazione attuale definisce solo i quattro campi mostrati.

Tieni la concorrenza separata dalla regola di unione

Questo calcolo è sincrono: legge gli argomenti, convalida i valori selezionati e restituisce un risultato oppure genera un errore. Non richiede task o actor per stabilire la precedenza. Swift descrive le funzioni asincrone come funzioni che possono sospendersi; aggiungere un’esecuzione asincrona non determinerebbe quale campo prevale. Concorrenza.

L’esempio dichiara PanelStyle Sendable per indicare che i suoi valori possono attraversare i confini di concorrenza. Apple richiede che una struct Sendable contenga membri Sendable. Questa conformità riguarda il trasferimento dei valori; le regole di selezione dei campi dipendono interamente da mergeStyles. Sendable.

Per questo compito locale relativo a un pannello di disegno, tieni il contratto accanto alla funzione e usa input di esempio chiaramente diversi. Il valore risultante deve poter essere spiegato campo per campo: un titolo in ingresso normalizzato, la spaziatura di base conservata, l’impostazione della griglia in ingresso e una didascalia in ingresso che può azzerare esplicitamente il valore precedente.

Fonti