Aby połączyć dwie wartości struktur Swift, napisz funkcję, która wybiera źródło dla każdego pola i zwraca nową wartość. Ten przykład dla macOS łączy dwa przykładowe style panelu rysowania, stosując jawne reguły zastępowania, zachowywania, czyszczenia wartości opcjonalnych i obsługi nieprawidłowych danych.

Określ priorytety przed napisaniem funkcji łączącej

Zadanie polega na obliczeniu na Macu jednego stylu panelu rysowania na podstawie dwóch istniejących wartości. Nazwij dane wejściowe base i incoming: nazwy te opisują ich role, a umowa określająca priorytet dla każdego pola rozstrzyga, która wartość wygrywa. Obliczenie wykorzystuje wyłącznie przykładowe wartości przechowywane w pamięci.

Swift opisuje struktury jako typy wartości i umożliwia tworzenie instancji za pomocą inicjalizatorów. Udokumentowane przez Apple API merge(_:uniquingKeysWith:) należy do Dictionary; rozstrzyga ono konflikty kluczy za pomocą domknięcia. W przypadku tej własnej struktury zdefiniuj funkcję łączącą, której implementacja wyraża zamierzone reguły dla pól. Struktury i klasy, Dictionary merge(_:uniquingKeysWith:).

Zastosuj poniższą przykładową umowę. Są to założenia projektowe na potrzeby tego przykładu, a nie zachowanie zapewniane automatycznie przez Swift.

Pole Reguła Przykładowy skutek
title Użyj tekstu z danych wejściowych, usuń otaczające spacje i znaki nowej linii, a następnie odrzuć pusty wynik " Tile Study \n" zmienia się na "Tile Study"
spacing Zachowaj wartość bazową; musi być nieujemna Wygrywa bazowe 12, a nie wejściowe 30
showsGrid Użyj wejściowej wartości logicznej bez zmian Wejściowe false zastępuje bazowe true
caption Użyj wejściowej wartości opcjonalnej bez zmian Wejściowe nil usuwa bazowy podpis

Tabela rozróżnia trzy operacje, które łatwo pomylić: wybór źródła, normalizację wybranej wartości i sprawdzenie poprawności wyniku. Wejściowe odstępy są celowo pomijane. Nie mogą zmienić wyniku, nawet jeśli różnią się od bazowych odstępów.

Metoda Foundation trimmingCharacters(in:) zwraca nowy ciąg znaków po usunięciu pasujących znaków z obu końców. Apple opisuje użycie .whitespacesAndNewlines do usuwania otaczających białych znaków; ciąg składający się wyłącznie z takich znaków staje się pusty. Odrzucenie pustego wyniku to dodatkowa reguła przyjęta w tym przykładzie. Foundation trimmingCharacters(in:).

Uruchom kompletny lokalny przykład na Macu

W tym przykładzie użyj toolchainu Swift 6. Swift.org udostępnia instrukcje instalacji dla macOS za pośrednictwem Swiftly i wskazuje Xcode jako opcję do tworzenia oprogramowania na platformy Apple. Otwórz Terminal i uruchom swift --version, aby sprawdzić, czy polecenie jest dostępne. Jeśli go brakuje, przed kontynuowaniem postępuj zgodnie z instrukcjami instalacji. Zainstaluj Swift na macOS, Pierwsze kroki.

Zapisz poniższy kompletny program w folderze roboczym jako MergeStyles.swift. Importuje Foundation, definiuje obie wartości wejściowe, łączy je, sprawdza oczekiwany wynik i testuje dwa przypadki odrzucenia. Obliczenie nie wykonuje żądań sieciowych ani nie zapisuje danych aplikacji.

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

W folderze zawierającym plik uruchom:

swift MergeStyles.swift

Oczekiwany wynik znajduje się poniżej. W środowisku badawczym nie było dostępnego Swift, dlatego program został przejrzany, ale nie został tam uruchomiony.

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

Inicjalizator stanowi główną pomoc w podejmowaniu decyzji: każdy argument wskazuje wybrane pole wejściowe albo nazwaną wartość pośrednią. Żadne z danych wejściowych nie jest modyfikowane. Semantyka wartości w Swift sprzyja temu podejściu; użyte tutaj pola również są typami wartości. Jeśli później dodasz pole typu klasowego, skopiowanie otaczającej struktury nie tworzy niezależnej kopii wskazywanej instancji klasy. Struktury i klasy.

Walidacja odbywa się przed utworzeniem instancji. Pusty tytuł wejściowy powoduje zgłoszenie błędu zamiast cichego przywrócenia tytułu bazowego, a ujemna bazowa wartość odstępów powoduje zgłoszenie błędu zamiast cichego ograniczenia jej do zera. Jeśli oba warunki nie są spełnione, najpierw zgłaszany jest emptyTitle, ponieważ ta instrukcja guard jest wykonywana jako pierwsza. Przed użyciem wyniku wywołujący powinien obsłużyć błąd.

Zdecyduj, czy wartość opcjonalna oznacza czyszczenie, czy zachowanie

W głównym przykładzie incoming.caption oznacza kompletny, oczekiwany podpis. Dlatego jego nil oznacza „wynik nie ma podpisu”. Dzięki temu czyszczenie jest jawnie określone w umowie, mimo że reprezentuje je zwykła wartość opcjonalna.

Inne wymaganie może zakładać, że wejściowe nil oznacza „zachowaj bazowy podpis”. W takim wariancie argument inicjalizatora odpowiadający za zastępowanie miałby postać caption: incoming.caption ?? base.caption. Operator Swift nil-coalescing wybiera lewy operand, jeśli zawiera wartość, a w przeciwnym razie używa prawego. To fragment zastępczy opisany etykietą, a nie drugi kompletny program. Operator nil-coalescing.

Ta reguła wartości domyślnej nie może jednocześnie wykorzystywać tego samego wejściowego nil do żądania wyczyszczenia podpisu. Zanim użyjesz tego wyrażenia, określ jego znaczenie. Pusty ciąg znaków to inna wartość: przy obu regułach wejściowy podpis "" pozostaje pustym podpisem, chyba że jawnie dodasz normalizację.

Jeśli przyszła operacja edycji ma obsługiwać wszystkie trzy działania — zachowanie, zastąpienie i wyczyszczenie — reprezentuj działanie osobno. Poniższy niezależny, kompletny program ilustruje operację dotyczącą wyłącznie podpisu, wykorzystując własny typ wyliczeniowy:

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

Ten typ wyliczeniowy to przykładowe rozszerzenie umowy. Gdy oba wejścia są kompletnymi stylami, zachowaj pierwotną funkcję przyjmującą dwie wartości; wprowadź typ działania, jeśli dane wejściowe rzeczywiście opisują zmiany.

Sprawdź priorytety, używając celowo różnych wartości

Wartościowy test łączenia sprawia, że konkurujące ze sobą pola mają różne wartości. Jeśli oba wejścia mają odstępy 12 i w obu włączona jest siatka, błędny wybór źródła może pozostać niezauważony. W próbce odstępy wynoszą 12 i 30, a ustawienia siatki to true i false, dzięki czemu oczekiwany wynik ujawnia podjęte decyzje.

Zanim dostosujesz funkcję, sprawdź poniższe przypadki brzegowe względem zamierzonej umowy:

  • Wejściowe false musi pozostać false; nie używaj operatora logicznego OR do wyrażenia zastępowania.
  • Podpis wejściowy nil musi wyczyścić podpis zgodnie z główną regułą.
  • Białe znaki otaczające tytuł muszą zostać usunięte, a spacje wewnętrzne — zachowane.
  • Tytuł wejściowy składający się wyłącznie z białych znaków musi spowodować błąd, bez przywracania wartości bazowej.
  • Bazowe odstępy 0 muszą być akceptowane; ujemna bazowa wartość odstępów musi powodować błąd.
  • Zamiana argumentów miejscami musi zastosować reguły do nowo przypisanych ról.

Ostatni punkt ma znaczenie, ponieważ ta funkcja łączenia zależy od kolejności argumentów. Po odwróceniu kolejności przykładowych danych wejściowych wynikiem będzie "Shape Study", odstępy 30, siatka true i podpis "Blue squares". Taki wynik wynika z podanych reguł. W wywołaniach jawnie nazywaj oba argumenty, aby ich role były widoczne.

Dodając pole, rozszerz tabelę, inicjalizator i test używający konkurujących wartości. Jeśli zagnieżdżony styl będzie zawierał kilka niezależnych ustawień, określ, czy zastępowana jest cała zagnieżdżona wartość, czy też każde z jego pól ma własny priorytet. Obecna implementacja określa zasady tylko dla czterech pokazanych pól.

Rozpatruj współbieżność niezależnie od reguł łączenia

To obliczenie jest synchroniczne: odczytuje argumenty, sprawdza wybrane wartości i zwraca wynik albo zgłasza błąd. Nie wymaga zadania ani aktora, by ustalić priorytety. Swift opisuje funkcje asynchroniczne jako funkcje, które mogą się zawiesić; dodanie wykonywania asynchronicznego nie rozstrzygnęłoby, które pole ma priorytet. Współbieżność.

Przykład deklaruje PanelStyle jako Sendable, wskazując, że jego wartości mogą przekraczać granice współbieżności. Apple wymaga, aby struktura Sendable zawierała elementy, które również są Sendable. Ta zgodność dotyczy przekazywania wartości; reguły wyboru pól nadal wynikają wyłącznie z mergeStyles. Sendable.

W przypadku tego lokalnego zadania z panelem rysowania umieść umowę obok funkcji i użyj wyraźnie różnych przykładowych danych wejściowych. Wynik powinien dać się wyjaśnić pole po polu: znormalizowany tytuł wejściowy, zachowane bazowe odstępy, wejściowe ustawienie siatki oraz wejściowy podpis, który może jawnie usunąć poprzednią wartość.

Źródła