0Pricing
Swift Academy · Lekcja

Efekty i zależności

Obsługa efektów ubocznych i wstrzykiwanie zależności.

Efekty i zależności to bezpłatna lekcja Swift Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Swift Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Swift Academy zawiera 4 lekcji w sumie.

Dlaczego istnieją efekty

Reducery muszą pozostać czyste: otrzymując stan i akcję, modyfikują wyłącznie stan. Jednak rzeczywiste aplikacje potrzebują efektów ubocznych, takich jak żądania sieciowe i timery. TCA modeluje je jako efekty zwracane przez reducer, dzięki czemu mutacje pozostają czyste, a praca asynchroniczna odbywa się osobno.

To rozdzielenie sprawia, że funkcjonalności można testować.

import ComposableArchitecture

// Reducer stays pure; side effects are values it returns.
// Returning .none means no further work.

Zwracanie .none

Gdy akcja zmienia tylko stan i nie wymaga dalszej pracy, reducer zwraca .none. Jest to najczęstszy efekt i sygnalizuje, że nie ma nic asynchronicznego do wykonania.

Ten przypadek został już przedstawiony; stanowi podstawę, do której wraca każda gałąź.

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .incrementTapped:
        state.count += 1
        return .none
    }
}

Effect.run dla pracy asynchronicznej

Aby wykonać pracę asynchroniczną, należy zwrócić .run. Jego domknięcie otrzymuje uchwyt send, za pomocą którego wyniki są przekazywane do systemu jako nowe akcje.

W tym przykładzie akcja pobierania wczytuje dane i po zakończeniu wysyła akcję odpowiedzi.

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .reloadTapped:
        return .run { send in
            let value = try await loadNumber()
            await send(.responseReceived(value))
        }
    case let .responseReceived(value):
        state.count = value
        return .none
    }
}

Przekazywanie wyników z powrotem jako akcji

Efekt nie może bezpośrednio modyfikować stanu; zamiast tego wysyła akcje za pośrednictwem send. Następnie reducer obsługuje te akcje i aktualizuje stan. Ta pętla sprawia, że każda mutacja odbywa się wewnątrz reduktora.

Odpowiedź sieciowa staje się więc akcją odpowiedzi, którą reducer zapisuje w stanie.

import ComposableArchitecture

.run { send in
    let result = try await fetch()
    await send(.dataLoaded(result))
}
// .dataLoaded is then handled by the reducer to set state.

Obsługa błędów w efektach

Praca asynchroniczna może zgłaszać błędy. Błędy można przechwycić wewnątrz efektu i wysłać akcję niepowodzenia, pozwalając reducerowi płynnie przejść do stanu błędu.

Opcjonalne domknięcie catch w .run otrzymuje zgłoszony błąd.

import ComposableArchitecture

.run { send in
    let value = try await loadNumber()
    await send(.responseReceived(value))
} catch: { error, send in
    await send(.loadFailed)
}

Dlaczego zależności mają znaczenie

Jeśli reducer bezpośrednio wywołuje klienta sieciowego lub odczytuje datę, testowanie i kontrolowanie go staje się trudne. TCA rozwiązuje ten problem za pomocą zależności: usług, które można wstrzykiwać i z których reducer korzysta przez @Dependency.

W testach można zastąpić je przewidywalnymi atrapami.

import ComposableArchitecture

// Instead of calling APIClient.shared directly,
// inject it as a dependency so tests can replace it.

Wrapper właściwości @Dependency

Wewnątrz reduktora zależność deklaruje się za pomocą @Dependency, wskazując ją ścieżką klucza w wartościach zależności. Biblioteka udostępnia wbudowane zależności, takie jak zegar i generator uuid.

W tym przykładzie reducer korzysta z zegara ciągłego.

import ComposableArchitecture

@Reducer
struct TimerFeature {
    @ObservableState
    struct State: Equatable { var seconds = 0 }
    enum Action { case start, tick }

    @Dependency(\.continuousClock) var clock

    var body: some ReducerOf<Self> {
        Reduce { state, action in .none }
    }
}

Używanie zależności w efekcie

Po zadeklarowaniu zależności używa się jej wewnątrz .run. Ponieważ zegar jest wstrzykiwany, testy mogą dostarczyć zegar testowy, który przesuwa czas na żądanie.

W tym przykładzie timer tyka co sekundę, korzystając ze wstrzykniętego zegara.

import ComposableArchitecture

var body: some ReducerOf<Self> {
    Reduce { state, action in
        switch action {
        case .start:
            return .run { send in
                for await _ in self.clock.timer(interval: .seconds(1)) {
                    await send(.tick)
                }
            }
        case .tick:
            state.seconds += 1
            return .none
        }
    }
}

Definiowanie własnej zależności

Własne zależności rejestruje się, dostosowując typ do DependencyKey i dostarczając wartość liveValue. Następnie do DependencyValues dodaje się obliczaną właściwość, aby zależność otrzymała ścieżkę klucza.

W tym przykładzie klient API staje się pełnoprawną zależnością.

import ComposableArchitecture

struct NumberClient {
    var fetch: () async throws -> Int
}

extension NumberClient: DependencyKey {
    static let liveValue = NumberClient {
        try await loadNumber()
    }
}

extension DependencyValues {
    var numberClient: NumberClient {
        get { self[NumberClient.self] }
        set { self[NumberClient.self] = newValue }
    }
}

Wstrzykiwanie własnej zależności

Mając gotową ścieżkę klucza, reducer deklaruje klienta za pomocą @Dependency(\.numberClient) i wywołuje go wewnątrz efektów. W środowisku produkcyjnym używana jest wartość działająca, a testy ją zastępują.

W tym przykładzie akcja ponownego wczytania pobiera dane za pośrednictwem wstrzykniętego klienta.

import ComposableArchitecture

@Reducer
struct NumberFeature {
    @ObservableState
    struct State: Equatable { var value = 0 }
    enum Action { case reload, response(Int) }

    @Dependency(\.numberClient) var numberClient

    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .reload:
                return .run { send in
                    let n = try await numberClient.fetch()
                    await send(.response(n))
                }
            case let .response(n):
                state.value = n
                return .none
            }
        }
    }
}

Anulowanie efektów

Długotrwałe efekty, takie jak timery, powinny umożliwiać anulowanie. Efekt oznacza się identyfikatorem cancellable, a następnie anuluje z poziomu innej akcji za pomocą .cancel.

W tym przypadku timer można zatrzymać na żądanie.

import ComposableArchitecture

enum CancelID { case timer }

// Start a cancellable effect:
.run { send in /* ... */ }
    .cancellable(id: CancelID.timer)

// Stop it from another action:
.cancel(id: CancelID.timer)

Szybkie sprawdzenie: efekty i wyniki

Przypomnij sobie, jak efekt przekazuje swój wynik z powrotem do reducera.

Podsumowanie: efekty i zależności

Nauczono się, jak TCA obsługuje efekty uboczne i wstrzykiwanie zależności:

  • Reducery zachowują czystość i zwracają wartości Effect; .none oznacza brak działania.
  • .run wykonuje pracę asynchroniczną i przekazuje wyniki z powrotem przez await send(.action).
  • Błędy obsługuje się za pomocą zamknięcia catch efektu.
  • @Dependency wstrzykuje usługi, takie jak zegar lub własne klienty, zadeklarowane za pomocą DependencyKey i DependencyValues.
  • Długotrwałe efekty można oznaczyć jako cancellable za pomocą identyfikatora.

Następnie funkcje zostaną połączone, a testy będą przeprowadzane wyczerpująco.

import ComposableArchitecture

// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.

Często zadawane pytania

Czy lekcja „Efekty i zależności” jest bezpłatna?

Tak — pełny tekst „Efekty i zależności” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Swift Academy, przejdź na CoddyKit PRO. Kurs Swift Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Efekty i zależności”?

Obsługa efektów ubocznych i wstrzykiwanie zależności. Ćwiczysz Swift Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Swift Academy?

Nie wymagamy żadnego doświadczenia. Swift Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Efekty i zależności”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Swift Academy?

Tak. Każda lekcja Swift Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Stan, akcja i reducer
  2. Store i integracja z SwiftUI
  3. Efekty i zależności
  4. Komponowanie i testowanie funkcji
← Powrót do Swift Academy