Para combinar dos valores de estructuras Swift, escribe una función que elija el origen de cada campo y devuelva un valor nuevo. Este ejemplo para macOS combina dos estilos sintéticos de panel de dibujo, con reglas explícitas para reemplazar, conservar, borrar valores opcionales y rechazar entradas no válidas.

Define qué prevalece antes de escribir la combinación

La tarea consiste en calcular un estilo de panel de dibujo en un Mac a partir de dos valores existentes. Llama a las entradas base y incoming: estos nombres describen sus funciones, mientras que un contrato campo por campo determina qué valor prevalece. El cálculo usa únicamente valores sintéticos en memoria.

Swift documenta las estructuras como tipos de valor y permite crear instancias mediante inicializadores. La API documentada por Apple merge(_:uniquingKeysWith:) pertenece a Dictionary; resuelve las claves duplicadas mediante un cierre. Para esta estructura personalizada, define una función de combinación cuya implementación exprese las reglas deseadas para los campos. Estructuras y clases, Dictionary merge(_:uniquingKeysWith:).

Usa el siguiente contrato ilustrativo. Son decisiones de diseño para este ejemplo, no comportamientos que Swift proporcione automáticamente.

Campo Regla Consecuencia del ejemplo
title Toma el texto entrante, elimina los espacios en blanco y saltos de línea de los extremos y luego rechaza un resultado vacío " Tile Study \n" se convierte en "Tile Study"
spacing Conserva el valor base; exige que no sea negativo El valor base 12 prevalece sobre el entrante 30
showsGrid Toma exactamente el booleano entrante El valor entrante false reemplaza al valor base true
caption Toma exactamente el valor opcional entrante El valor entrante nil borra el título del panel base

Esta tabla distingue tres operaciones que es fácil confundir: seleccionar un origen, normalizar el valor seleccionado y validar el resultado. El espaciado entrante se ignora deliberadamente. No puede cambiar el resultado, aunque su valor difiera del espaciado base.

trimmingCharacters(in:) de Foundation devuelve una cadena nueva a la que se le han quitado los caracteres coincidentes de ambos extremos. Apple documenta el uso de .whitespacesAndNewlines para el espacio en blanco circundante; una cadena formada solo por esos caracteres queda vacía. Rechazar ese resultado vacío es una regla adicional nuestra. Foundation trimmingCharacters(in:).

Ejecuta un ejemplo local completo en tu Mac

Usa una cadena de herramientas Swift 6 para este ejemplo. Swift.org ofrece instrucciones de instalación para macOS mediante Swiftly e indica que Xcode es una opción para el desarrollo en plataformas Apple. Abre Terminal y ejecuta swift --version para confirmar que el comando está disponible. Si no lo está, sigue las instrucciones de instalación antes de continuar. Instalar Swift en macOS, Introducción.

Guarda el siguiente programa completo como MergeStyles.swift en una carpeta de trabajo. Importa Foundation, define ambos valores de entrada, realiza la combinación, comprueba el resultado esperado y prueba los dos casos de rechazo. El cálculo no realiza solicitudes de red ni almacena datos de la aplicación.

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

Desde la carpeta que contiene el archivo, ejecuta:

swift MergeStyles.swift

A continuación se muestra el resultado esperado. Swift no estaba disponible en el entorno de investigación, por lo que el programa se inspeccionó, pero no se ejecutó allí.

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

El inicializador es la ayuda central para tomar decisiones: cada argumento identifica el campo de entrada seleccionado o un valor intermedio con nombre. Ninguna de las entradas se modifica. La semántica de valores de Swift respalda este enfoque; los campos de este ejemplo también son tipos de valor. Si más adelante introduces un campo cuyo valor sea una clase, copiar la estructura contenedora no crea una copia independiente de la instancia de clase referenciada. Estructuras y clases.

La validación ocurre antes de la construcción. Un título entrante en blanco genera un error en vez de recuperar silenciosamente el título base, y un espaciado base negativo genera un error en vez de reducirse silenciosamente a cero. Si ambas condiciones fallan, se informa primero de emptyTitle porque esa comprobación se ejecuta primero. Los llamadores deben gestionar el error antes de usar un resultado.

Decide si un valor opcional significa borrar o conservar

El ejemplo principal trata incoming.caption como el título del panel deseado completo. Por lo tanto, su valor nil significa «el resultado no tiene título de panel». Esto hace explícito el borrado en el contrato, aunque la representación sea simplemente un opcional.

Otro requisito podría establecer que el valor entrante nil significa «conservar el título del panel base». Para esa política, el argumento del inicializador de reemplazo sería caption: incoming.caption ?? base.caption. El operador de coalescencia nil de Swift selecciona su operando izquierdo si contiene un valor; de lo contrario, usa el derecho. Este es un fragmento de reemplazo etiquetado, no un segundo programa completo. Operador de coalescencia nil.

Esa política de reserva tampoco puede usar el mismo valor entrante nil para solicitar el borrado. Decide qué significa antes de adoptar la expresión. Una cadena vacía es otro valor distinto: con cualquiera de las dos políticas, un título de panel entrante "" sigue siendo un título de panel vacío, salvo que añadas explícitamente una normalización.

Cuando una operación de edición futura necesite las tres acciones —conservar, reemplazar y borrar—, representa la acción por separado. El siguiente programa independiente y completo ilustra una operación que solo afecta al título del panel y usa una enumeración personalizada:

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

Esta enumeración es una ampliación ilustrativa del contrato. Mantén la función original de dos valores cuando ambas entradas sean estilos completos; introduce un tipo de acción cuando la entrada describa realmente ediciones.

Comprueba la precedencia con valores deliberadamente distintos

Una buena comprobación de la combinación hace que los campos en conflicto tengan valores diferentes. Si ambas entradas usan un espaciado 12 y activan la cuadrícula, una elección incorrecta del origen puede pasar inadvertida. En cambio, el ejemplo usa 12 frente a 30, y true frente a false, de modo que el resultado esperado deja claras esas elecciones.

Antes de adaptar la función, comprueba estos casos límite con el contrato previsto:

  • El valor entrante false debe seguir siendo false; no uses OR booleano para expresar un reemplazo.
  • El título de panel entrante nil debe borrar el título según la política principal.
  • Se deben eliminar los espacios alrededor del título y conservar los espacios internos.
  • Un título entrante compuesto solo por espacios en blanco debe generar un error, sin recurrir al valor base.
  • El espaciado base 0 debe ser válido; un espaciado base negativo debe generar un error.
  • Al intercambiar los argumentos, las reglas deben aplicarse a las nuevas funciones asignadas.

El último punto importa porque esta combinación depende del orden. Al invertir las entradas del ejemplo, se seleccionan "Shape Study", el espaciado 30, la cuadrícula true y el título del panel "Blue squares". Ese es el resultado de las reglas indicadas. Nombra ambos argumentos en los puntos de llamada para que sus funciones queden claras.

Al añadir un campo, amplía la tabla y el inicializador, y añade una comprobación con valores en conflicto. Si un estilo anidado futuro contiene varios ajustes independientes, decide si se reemplaza todo el valor anidado o si cada uno de sus campos tiene su propia precedencia. La implementación actual solo define los cuatro campos mostrados.

Mantén la concurrencia separada de la política de combinación

Este cálculo es síncrono: lee sus argumentos, valida los valores seleccionados y devuelve un resultado o genera un error. No necesita ninguna tarea ni actor para establecer la precedencia. Swift describe las funciones asíncronas como funciones que pueden suspenderse; añadir ejecución asíncrona no determinaría qué campo prevalece. Concurrencia.

El ejemplo declara PanelStyle como Sendable para expresar que sus valores pueden cruzar límites de concurrencia. Apple exige que una estructura Sendable contenga miembros que también sean Sendable. Esa conformidad se refiere a la transferencia de valores; las reglas para seleccionar campos siguen dependiendo por completo de mergeStyles. Sendable.

Para esta tarea local de panel de dibujo, mantén el contrato junto a la función y usa entradas de ejemplo con valores claramente distintos. El resultado debe poder explicarse campo por campo: un título entrante normalizado, el espaciado base conservado, la configuración entrante de la cuadrícula y un título de panel entrante que pueda borrar explícitamente el valor anterior.

Fuentes