Swift Academy · Lektion

Effekter och beroenden

Hantera sidoeffekter och injicera beroenden.

Lektion 3 av 413 steg

Effekter och beroenden är en gratis lektion i Swift Academy på CoddyKit. Detta är lektion 3 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Swift Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Swift Academy innehåller totalt 4 lektioner.

Varför Effects finns

Reducers måste förbli rena: givet ett state och en action får de bara mutera state. Men riktiga appar behöver sidoeffekter som nätverksanrop och timers. TCA modellerar dessa som Effects som reducern returnerar, så att mutationen förblir ren medan det asynkrona arbetet körs separat.

Det är den här uppdelningen som gör features testbara.

import ComposableArchitecture

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

Returnera .none

När en action bara ändrar state och inte kräver något efterföljande arbete returnerar reducern .none. Det här är den vanligaste effekten och signalerar att det inte finns något asynkront arbete att utföra.

Du har redan sett detta; det är grundfallet som varje gren faller tillbaka på.

import ComposableArchitecture

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

Effect.run för asynkront arbete

För att utföra asynkront arbete returnerar du .run. Dess closure tar emot ett send-handtag som du använder för att skicka tillbaka resultat till systemet som nya actions.

Här läser en fetch-action in data och skickar en response-action när arbetet är klart.

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

Skicka tillbaka resultat som Actions

En effect kan inte mutera state direkt, utan skickar i stället actions via send. Reducern hanterar sedan dessa actions och uppdaterar state. Den här loopen håller alla mutationer inne i reducern.

Ett nätverkssvar blir alltså en response-action som reducern lagrar.

import ComposableArchitecture

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

Hantera fel i Effects

Asynkront arbete kan kasta fel. Du kan fånga fel inne i effekten och skicka en failure-action, så att reducern kan gå över till ett feltillstånd på ett tydligt sätt.

Den valfria catch-closuren på .run tar emot det kastade felet.

import ComposableArchitecture

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

Varför Dependencies är viktiga

Om en reducer anropar en nätverksklient eller läser av datumet direkt blir den svår att testa och styra. TCA löser detta med dependencies: injicerbara tjänster som reducern använder via @Dependency.

I tester ersätter du dem med förutsägbara testobjekt.

import ComposableArchitecture

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

Property-wrappern @Dependency

Inuti en reducer deklarerar du en dependency med @Dependency och anger den via dess key path i dependency-värdena. Biblioteket tillhandahåller inbyggda dependencies som en klocka och en UUID-generator.

Här hämtar reducern den kontinuerliga klockan.

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

Använda en Dependency i en Effect

När den är deklarerad använder du dependencyn inuti .run. Eftersom klockan injiceras kan tester tillhandahålla en testklocka som avancerar tiden på begäran.

Här tickar timern varje sekund med hjälp av den injicerade klockan.

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

Definiera en egen Dependency

Du registrerar egna dependencies genom att låta ett värde följa DependencyKey och tillhandahålla ett liveValue. Sedan lägger du till en beräknad property i DependencyValues så att den får en key path.

Här blir en API-klient en förstaklassens dependency.

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

Injicera den egna Dependencyn

När key path finns på plats deklarerar reducern klienten med @Dependency(\.numberClient) och anropar den i effects. I produktion används live-värdet; tester kan ersätta det.

Här hämtar en reload-action data via den injicerade klienten.

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

Avbryta effekter

Långvariga effekter, som timers, bör kunna avbrytas. Märk en effekt med ett cancellable-id och avbryt den senare från en annan åtgärd med .cancel.

Här kan timern stoppas på begäran.

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)

Snabb kontroll: effekter och resultat

Kom ihåg hur en effekt kommunicerar sitt resultat tillbaka till reducern.

Sammanfattning: effekter och beroenden

Ni har lärt er hur TCA hanterar sidoeffekter och injicering:

  • Reducers förblir rena och returnerar Effect-värden; .none betyder att inget arbete ska utföras.
  • .run utför asynkront arbete och skickar tillbaka resultat via await send(.action).
  • Fel hanteras med effektens catch-closure.
  • @Dependency injicerar tjänster som klockan eller era egna klienter, deklarerade via DependencyKey och DependencyValues.
  • Långvariga effekter kan göras cancellable med hjälp av ett id.

Härnäst ska ni sätta samman features och testa dem uttömmande.

import ComposableArchitecture

// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.
Gratis att börja

Lär dig Swift med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
122
Lektioner
409

Vanliga frågor

Är lektionen ”Effekter och beroenden” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Swift Academy, inklusive ”Effekter och beroenden”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Swift Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Effekter och beroenden”?

Hantera sidoeffekter och injicera beroenden. Ni övar på Swift Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Swift Academy?

Du behöver inga förkunskaper. Utbildningen i Swift Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Effekter och beroenden”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Swift Academy-lektionen?

Ja. Varje Swift Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. State, Action och Reducer
  2. Store och SwiftUI-integration
  3. Effekter och beroenden
  4. Sätta samman och testa funktioner
← Tillbaka till Swift Academy