ComposableArchitecture Documentation

Structure Effect

public struct Effect<Output, Failure: Error>: Publisher  

The Effect type encapsulates a unit of work that can be run in the outside world, and can feed data back to the Store. It is the perfect place to do side effects, such as network requests, saving/loading from disk, creating timers, interacting with dependencies, and more.

Effects are returned from reducers so that the Store can perform the effects after the reducer is done running. It is important to note that Store is not thread safe, and so all effects must receive values on the same thread, and if the store is being used to drive UI then it must receive values on the main thread.

An effect simply wraps a Publisher value and provides some convenience initializers for constructing some common types of effects.

%105 Effect Effect Publisher Publisher Effect->Publisher

Nested Types

Effect.Subscriber

Conforms To

Publisher

Initializers

init(_:​)

public init<P: Publisher>(_ publisher: P) where P.Output == Output, P.Failure == Failure  

Initializes an effect that wraps a publisher. Each emission of the wrapped publisher will be emitted by the effect.

This initializer is useful for turning any publisher into an effect. For example:

Effect(
  NotificationCenter.default
    .publisher(for: UIApplication.userDidTakeScreenshotNotification)
)

Alternatively, you can use the .eraseToEffect() method that is defined on the Publisher protocol:

NotificationCenter.default
  .publisher(for: UIApplication.userDidTakeScreenshotNotification)
  .eraseToEffect()

Parameters

publisher P

A publisher.

init(value:​)

public init(value: Output)  

Initializes an effect that immediately emits the value passed in.

Parameters

value Output

The value that is immediately emitted by the effect.

init(error:​)

public init(error: Failure)  

Initializes an effect that immediately fails with the error passed in.

Parameters

error Failure

The error that is immediately emitted by the effect.

Properties

upstream

public let upstream: AnyPublisher<Output, Failure>

none

public static var none: Effect  

An effect that does nothing and completes immediately. Useful for situations where you must return an effect, but you don't need to do anything.

Methods

receive(subscriber:​)

public func receive<S>(
    subscriber: S
  ) where S: Combine.Subscriber, Failure == S.Failure, Output == S.Input  

future(_:​)

public static func future(
    _ attemptToFulfill: @escaping (@escaping (Result<Output, Failure>) -> Void) -> Void
  ) -> Effect  

Creates an effect that can supply a single value asynchronously in the future.

This can be helpful for converting APIs that are callback-based into ones that deal with Effects.

For example, to create an effect that delivers an integer after waiting a second:

Effect<Int, Never>.future { callback in
  DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    callback(.success(42))
  }
}

Note that you can only deliver a single value to the callback. If you send more they will be discarded:

Effect<Int, Never>.future { callback in
  DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    callback(.success(42))
    callback(.success(1729)) // Will not be emitted by the effect
  }
}

If you need to deliver more than one value to the effect, you should use the Effect initializer that accepts a Subscriber value.

Parameters

attempt​ToFulfill @escaping (@escaping (Result<Output, Failure>) -> Void) -> Void

A closure that takes a callback as an argument which can be used to feed it Result<Output, Failure> values.

result(_:​)

public static func result(_ attemptToFulfill: @escaping () -> Result<Output, Failure>) -> Self  

Initializes an effect that lazily executes some work in the real world and synchronously sends that data back into the store.

For example, to load a user from some JSON on the disk, one can wrap that work in an effect:

Effect<User, Error>.result {
  let fileUrl = URL(
    fileURLWithPath: NSSearchPathForDirectoriesInDomains(
      .documentDirectory, .userDomainMask, true
    )[0]
  )
  .appendingPathComponent("user.json")

  let result = Result<User, Error> {
    let data = try Data(contentsOf: fileUrl)
    return try JSONDecoder().decode(User.self, from: $0)
  }

  return result
}

Parameters

attempt​ToFulfill @escaping () -> Result<Output, Failure>

A closure encapsulating some work to execute in the real world.

Returns

An effect.

run(_:​)

public static func run(
    _ work: @escaping (Effect.Subscriber) -> Cancellable
  ) -> Self  

Initializes an effect from a callback that can send as many values as it wants, and can send a completion.

This initializer is useful for bridging callback APIs, delegate APIs, and manager APIs to the Effect type. One can wrap those APIs in an Effect so that its events are sent through the effect, which allows the reducer to handle them.

For example, one can create an effect to ask for access to MPMediaLibrary. It can start by sending the current status immediately, and then if the current status is notDetermined it can request authorization, and once a status is received it can send that back to the effect:

Effect.run { subscriber in
  subscriber.send(MPMediaLibrary.authorizationStatus())

  guard MPMediaLibrary.authorizationStatus() == .notDetermined else {
    subscriber.send(completion: .finished)
    return AnyCancellable {}
  }

  MPMediaLibrary.requestAuthorization { status in
    subscriber.send(status)
    subscriber.send(completion: .finished)
  }
  return AnyCancellable {
    // Typically clean up resources that were created here, but this effect doesn't
    // have any.
  }
}

Parameters

work @escaping (Effect.​Subscriber) -> Cancellable

A closure that accepts a Subscriber value and returns a cancellable. When the Effect is completed, the cancellable will be used to clean up any resources created when the effect was started.

concatenate(_:​)

public static func concatenate(_ effects: Effect...) -> Effect  

Concatenates a variadic list of effects together into a single effect, which runs the effects one after the other.

Parameters

effects Effect

A variadic list of effects.

Returns

A new effect

concatenate(_:​)

public static func concatenate<C: Collection>(
    _ effects: C
  ) -> Effect where C.Element == Effect  

Concatenates a collection of effects together into a single effect, which runs the effects one after the other.

Parameters

effects C

A collection of effects.

Returns

A new effect

merge(_:​)

public static func merge(
    _ effects: Effect...
  ) -> Effect  

Merges a variadic list of effects together into a single effect, which runs the effects at the same time.

Parameters

effects Effect

A list of effects.

Returns

A new effect

merge(_:​)

public static func merge<S: Sequence>(_ effects: S) -> Effect where S.Element == Effect  

Merges a sequence of effects together into a single effect, which runs the effects at the same time.

Parameters

effects S

A sequence of effects.

Returns

A new effect

fire​And​Forget(_:​)

public static func fireAndForget(_ work: @escaping () -> Void) -> Effect  

Creates an effect that executes some work in the real world that doesn't need to feed data back into the store.

Parameters

work @escaping () -> Void

A closure encapsulating some work to execute in the real world.

Returns

An effect.

map(_:​)

public func map<T>(_ transform: @escaping (Output) -> T) -> Effect<T, Failure>  

Transforms all elements from the upstream effect with a provided closure.

Parameters

transform @escaping (Output) -> T

A closure that transforms the upstream effect's output to a new output.

Returns

A publisher that uses the provided closure to map elements from the upstream effect to new elements that it then publishes.

cancellable(id:​cancel​InFlight:​)

public func cancellable(id: AnyHashable, cancelInFlight: Bool = false) -> Effect  

Turns an effect into one that is capable of being canceled.

To turn an effect into a cancellable one you must provide an identifier, which is used in Effect/cancel(id:) to identify which in-flight effect should be canceled. Any hashable value can be used for the identifier, such as a string, but you can add a bit of protection against typos by defining a new type that conforms to Hashable, such as an empty struct:

struct LoadUserId: Hashable {}

case .reloadButtonTapped:
  // Start a new effect to load the user
  return environment.loadUser
    .map(Action.userResponse)
    .cancellable(id: LoadUserId(), cancelInFlight: true)

case .cancelButtonTapped:
  // Cancel any in-flight requests to load the user
  return .cancel(id: LoadUserId())

Parameters

id Any​Hashable

The effect's identifier.

cancel​InFlight Bool

Determines if any in-flight effect with the same identifier should be canceled before starting this new one.

Returns

A new effect that is capable of being canceled by an identifier.

cancel(id:​)

public static func cancel(id: AnyHashable) -> Effect  

An effect that will cancel any currently in-flight effect with the given identifier.

Parameters

id Any​Hashable

An effect identifier.

Returns

A new effect that will cancel any currently in-flight effect with the given identifier.

cancel(ids:​)

@_disfavoredOverload
  public static func cancel(ids: AnyHashable...) -> Effect  

An effect that will cancel multiple currently in-flight effects with the given identifiers.

Parameters

ids Any​Hashable

A variadic list of effect identifiers.

Returns

A new effect that will cancel any currently in-flight effects with the given identifiers.

cancel(ids:​)

public static func cancel<S: Sequence>(ids: S) -> Effect where S.Element == AnyHashable  

An effect that will cancel multiple currently in-flight effects with the given identifiers.

Parameters

ids S

A sequence of effect identifiers.

Returns

A new effect that will cancel any currently in-flight effects with the given identifiers.

task(priority:​operation:​_:​)

public static func task(
      priority: TaskPriority? = nil,
      operation: @escaping @Sendable () async -> Output
    ) -> Self where Failure == Never  

Wraps an asynchronous unit of work in an effect.

This function is useful for executing work in an asynchronous context and capture the result in an Effect so that the reducer, a non-asynchronous context, can process it.

Effect.task {
  guard case let .some((data, _)) = try? await URLSession.shared
    .data(from: .init(string: "http://numbersapi.com/42")!)
  else {
    return "Could not load"
  }

  return String(decoding: data, as: UTF8.self)
}

Note that due to the lack of tools to control the execution of asynchronous work in Swift, it is not recommended to use this function in reducers directly. Doing so will introduce thread hops into your effects that will make testing difficult. You will be responsible for adding explicit expectations to wait for small amounts of time so that effects can deliver their output.

Instead, this function is most helpful for calling async/await functions from the live implementation of dependencies, such as URLSession.data, MKLocalSearch.start and more.

Parameters

priority Task​Priority?

Priority of the underlying task. If nil, the priority will come from Task.currentPriority.

operation @escaping @Sendable ()

The operation to execute.

Returns

An effect wrapping the given asynchronous work.

debounce(id:​for:​scheduler:​options:​)

public func debounce<S: Scheduler>(
    id: AnyHashable,
    for dueTime: S.SchedulerTimeType.Stride,
    scheduler: S,
    options: S.SchedulerOptions? = nil
  ) -> Effect  

Turns an effect into one that can be debounced.

To turn an effect into a debounce-able one you must provide an identifier, which is used to determine which in-flight effect should be canceled in order to start a new effect. Any hashable value can be used for the identifier, such as a string, but you can add a bit of protection against typos by defining a new type that conforms to Hashable, such as an empty struct:

case let .textChanged(text):
  struct SearchId: Hashable {}

  return environment.search(text)
    .debounce(id: SearchId(), for: 0.5, scheduler: environment.mainQueue)
    .map(Action.searchResponse)

Parameters

id Any​Hashable

The effect's identifier.

due​Time S.​Scheduler​Time​Type.​Stride

The duration you want to debounce for.

scheduler S

The scheduler you want to deliver the debounced output to.

options S.​Scheduler​Options?

Scheduler options that customize the effect's delivery of elements.

Returns

An effect that publishes events only after a specified time elapses.

deferred(for:​scheduler:​options:​)

public func deferred<S: Scheduler>(
    for dueTime: S.SchedulerTimeType.Stride,
    scheduler: S,
    options: S.SchedulerOptions? = nil
  ) -> Effect  

Returns an effect that will be executed after given dueTime.

case let .textChanged(text):
  return environment.search(text)
    .deferred(for: 0.5, scheduler: environment.mainQueue)
    .map(Action.searchResponse)

Parameters

upstream

the effect you want to defer.

due​Time S.​Scheduler​Time​Type.​Stride

The duration you want to defer for.

scheduler S

The scheduler you want to deliver the defer output to.

options S.​Scheduler​Options?

Scheduler options that customize the effect's delivery of elements.

Returns

An effect that will be executed after dueTime

throttle(id:​for:​scheduler:​latest:​)

public func throttle<S>(
    id: AnyHashable,
    for interval: S.SchedulerTimeType.Stride,
    scheduler: S,
    latest: Bool
  ) -> Effect where S: Scheduler  

Throttles an effect so that it only publishes one output per given interval.

Parameters

id Any​Hashable

The effect's identifier.

interval S.​Scheduler​Time​Type.​Stride

The interval at which to find and emit the most recent element, expressed in the time system of the scheduler.

scheduler S

The scheduler you want to deliver the throttled output to.

latest Bool

A boolean value that indicates whether to publish the most recent element. If false, the publisher emits the first element received during the interval.

Returns

An effect that emits either the most-recent or first element received during the specified interval.

failing(_:​)

public static func failing(_ prefix: String) -> Self  

An effect that causes a test to fail if it runs.

This effect can provide an additional layer of certainty that a tested code path does not execute a particular effect.

For example, let's say we have a very simple counter application, where a user can increment and decrement a number. The state and actions are simple enough:

struct CounterState: Equatable {
  var count = 0
}

enum CounterAction: Equatable {
  case decrementButtonTapped
  case incrementButtonTapped
}

Let's throw in a side effect. If the user attempts to decrement the counter below zero, the application should refuse and play an alert sound instead.

We can model playing a sound in the environment with an effect:

struct CounterEnvironment {
  let playAlertSound: () -> Effect<Never, Never>
}

Now that we've defined the domain, we can describe the logic in a reducer:

let counterReducer = Reducer<
  CounterState, CounterAction, CounterEnvironment
> { state, action, environment in
  switch action {
  case .decrementButtonTapped:
    if state > 0 {
      state.count -= 0
      return .none
    } else {
      return environment.playAlertSound()
        .fireAndForget()
    }

  case .incrementButtonTapped:
    state.count += 1
    return .non
  }
}

Let's say we want to write a test for the increment path. We can see in the reducer that it should never play an alert, so we can configure the environment with an effect that will fail if it ever executes:

func testIncrement() {
  let store = TestStore(
    initialState: CounterState(count: 0)
    reducer: counterReducer,
    environment: CounterEnvironment(
      playSound: .failing("playSound")
    )
  )

  store.send(.increment) {
    $0.count = 1
  }
}

By using a .failing effect in our environment we have strengthened the assertion and made the test easier to understand at the same time. We can see, without consulting the reducer itself, that this particular action should not access this effect.

Parameters

prefix String

A string that identifies this scheduler and will prefix all failure messages.

Returns

An effect that causes a test to fail if it runs.