0Pricing
Swift Academy · Lezione

Effetti e dipendenze

Gestisci gli effetti collaterali e inietti le dipendenze.

Effetti e dipendenze è una lezione Swift Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Swift Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Swift Academy include 4 lezioni in totale.

Perché esistono gli effetti

I reducer devono rimanere puri: dato uno stato e un’action, modificano soltanto lo stato. Tuttavia, le app reali richiedono effetti collaterali come richieste di rete e timer. TCA li rappresenta come Effect restituiti dal reducer, mantenendo pura la modifica mentre il lavoro asincrono viene eseguito separatamente.

È questa separazione a rendere le feature verificabili.

import ComposableArchitecture

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

Restituzione di .none

Quando un’action modifica soltanto lo stato e non richiede altro lavoro, il reducer restituisce .none. Questo è l’effect più comune e indica che non c’è nulla da eseguire in modo asincrono.

Lo ha già visto: è il comportamento di base a cui torna ogni ramo.

import ComposableArchitecture

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

Effect.run per il lavoro asincrono

Per eseguire lavoro asincrono si restituisce .run. La relativa closure riceve un handle send, che si utilizza per reinserire i risultati nel sistema come nuove action.

In questo caso, un’action di fetch carica i dati e invia un’action di risposta al termine.

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

Restituzione dei risultati come action

Un effect non può modificare direttamente lo stato; invia invece action tramite send. Il reducer gestisce quindi queste action e aggiorna lo stato. Questo ciclo mantiene ogni mutazione all’interno del reducer.

Una risposta di rete diventa così un’action di risposta che il reducer salva.

import ComposableArchitecture

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

Gestione degli errori negli effect

Il lavoro asincrono può generare errori. È possibile intercettarli all’interno dell’effect e inviare un’action di errore, consentendo al reducer di passare ordinatamente a uno stato di errore.

La closure catch opzionale di .run riceve l’errore generato.

import ComposableArchitecture

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

Perché le dipendenze sono importanti

Se un reducer chiama direttamente un client di rete o legge direttamente la data, diventa difficile da testare e controllare. TCA risolve il problema con le dependencies: servizi iniettabili a cui il reducer accede tramite @Dependency.

Nei test è possibile sostituirli con implementazioni fittizie e prevedibili.

import ComposableArchitecture

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

Il property wrapper @Dependency

All’interno di un reducer si dichiara una dipendenza con @Dependency, indicandola tramite il relativo key path nei dependency values. La libreria fornisce elementi integrati come un clock e un generatore di uuid.

In questo caso, il reducer utilizza il clock continuo.

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

Uso di una dipendenza in un effect

Una volta dichiarata, si utilizza la dipendenza all’interno di .run. Poiché il clock viene iniettato, i test possono fornire un clock di test che fa avanzare il tempo su richiesta.

In questo caso, il timer emette un impulso ogni secondo utilizzando il clock iniettato.

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

Definizione di una dipendenza personalizzata

Le dipendenze personalizzate si registrano rendendo un valore conforme a DependencyKey e fornendo un liveValue. Si aggiunge quindi una proprietà calcolata a DependencyValues, così da assegnarle un key path.

In questo caso, un client API diventa una dipendenza di prima classe.

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

Iniezione della dipendenza personalizzata

Una volta disponibile il key path, il reducer dichiara il client con @Dependency(\.numberClient) e lo chiama all’interno degli effect. In produzione viene usato il valore live; nei test viene sovrascritto.

In questo caso, un’action di ricaricamento esegue il fetch tramite il client iniettato.

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

Annullamento degli effetti

Gli effetti di lunga durata, come i timer, dovrebbero poter essere annullati. È possibile contrassegnare un effetto con un identificatore cancellable e annullarlo in seguito da un'altra action con .cancel.

In questo caso, il timer può essere arrestato su richiesta.

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)

Verifica rapida: effetti e risultati

Ricordi come un effetto comunica il proprio risultato al reducer.

Riepilogo: effetti e dipendenze

Ha imparato come TCA gestisce gli effetti collaterali e l'iniezione delle dipendenze:

  • I reducer rimangono puri e restituiscono valori Effect; .none indica che non c'è alcun lavoro da eseguire.
  • .run esegue il lavoro asincrono e restituisce i risultati tramite await send(.action).
  • Gli errori vengono gestiti con la closure catch dell'effetto.
  • @Dependency inietta servizi come l'orologio o i client definiti da Lei, dichiarati tramite DependencyKey e DependencyValues.
  • Gli effetti di lunga durata possono essere resi cancellable tramite un identificatore.

Ora unirà più funzionalità e le testerà in modo esaustivo.

import ComposableArchitecture

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

Domande Frequenti

La lezione «Effetti e dipendenze» è gratuita?

Sì — il testo completo di «Effetti e dipendenze» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Swift Academy, passa a CoddyKit PRO. Il corso Swift Academy include 4 lezioni in totale.

Cosa imparerò in «Effetti e dipendenze»?

Gestisci gli effetti collaterali e inietti le dipendenze. Eserciti Swift Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Swift Academy?

Non è richiesta alcuna esperienza precedente. Swift Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Effetti e dipendenze»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Swift Academy?

Sì. Ogni lezione Swift Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Stato, azione e reducer
  2. Lo Store e l’integrazione con SwiftUI
  3. Effetti e dipendenze
  4. Composizione e test delle funzionalità
← Torna a Swift Academy