Effect

public struct Effect<Output, Failure> : Publisher where Failure : Error
extension Effect: CustomDebugOutputConvertible

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.

  • Undocumented

    Declaration

    Swift

    public let upstream: AnyPublisher<Output, 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()
    

    Declaration

    Swift

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

    Parameters

    publisher

    A publisher.

  • Declaration

    Swift

    public func receive<S>(
      subscriber: S
    ) where S: Combine.Subscriber, Failure == S.Failure, Output == S.Input
  • Initializes an effect that immediately emits the value passed in.

    Declaration

    Swift

    public init(value: Output)

    Parameters

    value

    The value that is immediately emitted by the effect.

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

    Declaration

    Swift

    public init(error: Failure)

    Parameters

    error

    The error that is immediately emitted by the 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.

    Declaration

    Swift

    public static var none: Effect { get }
  • 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.

    Declaration

    Swift

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

    Parameters

    attemptToFulfill

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

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

    Declaration

    Swift

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

    Parameters

    attemptToFulfill

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

    Return Value

    An effect.

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

    Declaration

    Swift

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

    Parameters

    work

    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.

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

    Warning

    Combine’s Publishers.Concatenate operator, which this function uses, can leak when its suffix is a Publishers.MergeMany operator, which is used throughout the Composable Architecture in functions like Reducer.combine.

    Feedback filed: https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58

    Declaration

    Swift

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

    Parameters

    effects

    A variadic list of effects.

    Return Value

    A new effect

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

    Warning

    Combine’s Publishers.Concatenate operator, which this function uses, can leak when its suffix is a Publishers.MergeMany operator, which is used throughout the Composable Architecture in functions like Reducer.combine.

    Feedback filed: https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58

    Declaration

    Swift

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

    Parameters

    effects

    A collection of effects.

    Return Value

    A new effect

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

    Declaration

    Swift

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

    Parameters

    effects

    A list of effects.

    Return Value

    A new effect

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

    Declaration

    Swift

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

    Parameters

    effects

    A sequence of effects.

    Return Value

    A new effect

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

    Declaration

    Swift

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

    Parameters

    work

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

    Return Value

    An effect.

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

    Declaration

    Swift

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

    Parameters

    transform

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

    Return Value

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

  • 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())
    

    Declaration

    Swift

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

    Parameters

    id

    The effect’s identifier.

    cancelInFlight

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

    Return Value

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

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

    Declaration

    Swift

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

    Parameters

    id

    An effect identifier.

    Return Value

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

  • 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)
        .map(Action.searchResponse)
        .debounce(id: SearchId(), for: 0.5, scheduler: environment.mainQueue)
    

    Declaration

    Swift

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

    Parameters

    id

    The effect’s identifier.

    dueTime

    The duration you want to debounce for.

    scheduler

    The scheduler you want to deliver the debounced output to.

    options

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

    Return Value

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

  • Undocumented

    See more

    Declaration

    Swift

    public struct Subscriber
  • Undocumented

    Declaration

    Swift

    public var debugOutput: String { get }

Available where Failure == Swift.Error

  • 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>.catching {
      let fileUrl = URL(
        fileURLWithPath: NSSearchPathForDirectoriesInDomains(
          .documentDirectory, .userDomainMask, true
        )[0]
      )
      .appendingPathComponent("user.json")
    
      let data = try Data(contentsOf: fileUrl)
      return try JSONDecoder().decode(User.self, from: $0)
    }
    

    Declaration

    Swift

    public static func catching(_ work: @escaping () throws -> Output) -> Effect<Output, Failure>

    Parameters

    work

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

    Return Value

    An effect.

Available where Output == Never

  • Upcasts an Effect<Never, Failure> to an Effect<T, Failure> for any type T. This is possible to do because an Effect<Never, Failure> can never produce any values to feed back into the store (hence the name “fire and forget”), and therefore we can act like it’s an effect that produces values of any type (since it never produces values).

    This is useful for times you have an Effect<Never, Failure> but need to massage it into another type in order to return it from a reducer:

    case .buttonTapped:
      return analyticsClient.track("Button Tapped")
        .fireAndForget()
    

    Declaration

    Swift

    public func fireAndForget<T>() -> Effect<T, Failure>

    Return Value

    An effect.

Available where Failure == Never

  • Returns an effect that repeatedly emits the current time of the given scheduler on the given interval.

    While it is possible to use Foundation’s Timer.publish(every:tolerance:on:in:options:) API to create a timer in the Composable Architecture, it is not advisable. This API only allows creating a timer on a run loop, which means when writing tests you will need to explicitly wait for time to pass in order to see how the effect evolves in your feature.

    In the Composable Architecture we test time-based effects like this by using the TestScheduler, which allows us to explicitly and immediately advance time forward so that we can see how effects emit. However, because Timer.publish takes a concrete RunLoop as its scheduler, we can’t substitute in a TestScheduler during tests`.

    That is why we provide the Effect.timer effect. It allows you to create a timer that works with any scheduler, not just a run loop, which means you can use a DispatchQueue or RunLoop when running your live app, but use a TestScheduler in tests.

    To start and stop a timer in your feature you can create the timer effect from an action and then use the .cancel(id:) effect to stop the timer:

    struct AppState {
      var count = 0
    }
    
    enum AppAction {
      case startButtonTapped, stopButtonTapped, timerTicked
    }
    
    struct AppEnvironment {
      var mainQueue: AnySchedulerOf<DispatchQueue>
    }
    
    let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in
      struct TimerId: Hashable {}
    
      switch action {
      case .startButtonTapped:
        return Effect.timer(id: TimerId(), every: 1, on: env.mainQueue)
          .map { _ in .timerTicked }
    
      case .stopButtonTapped:
        return .cancel(id: TimerId())
    
      case let .timerTicked:
        state.count += 1
        return .none
    }
    

    Then to test the timer in this feature you can use a test scheduler to advance time:

    func testTimer() { let scheduler = DispatchQueue.testScheduler

    let store = TestStore(
      initialState: .init(),
      reducer: appReducer,
      envirnoment: .init(
        mainQueue: scheduler.eraseToAnyScheduler()
      )
    )
    
    store.assert(
      .send(.startButtonTapped),
    
      .do { scheduler.advance(by: .seconds(1)) },
      .receive(.timerTicked) { $0.count = 1 },
    
      .do { scheduler.advance(by: .seconds(5)) },
      .receive(.timerTicked) { $0.count = 2 },
      .receive(.timerTicked) { $0.count = 3 },
      .receive(.timerTicked) { $0.count = 4 },
      .receive(.timerTicked) { $0.count = 5 },
      .receive(.timerTicked) { $0.count = 6 },
    
      .send(.stopButtonTapped)
    )
    

    }

    Note

    This effect is only meant to be used with features built in the Composable Architecture, and returned from a reducer. If you want a testable alternative to Foundation’s Timer.publish you can use the publisher Publishers.Timer that is included in this library via the CombineSchedulers module.

    Declaration

    Swift

    public static func timer<S>(
      id: AnyHashable,
      every interval: S.SchedulerTimeType.Stride,
      tolerance: S.SchedulerTimeType.Stride? = nil,
      on scheduler: S,
      options: S.SchedulerOptions? = nil
    ) -> Effect where S: Scheduler, S.SchedulerTimeType == Output

    Parameters

    interval

    The time interval on which to publish events. For example, a value of 0.5 publishes an event approximately every half-second.

    scheduler

    The scheduler on which the timer runs.

    tolerance

    The allowed timing variance when emitting events. Defaults to nil, which allows any variance.

    options

    Scheduler options passed to the timer. Defaults to nil.