Effect - mbrandonw/swift-composable-architecture GitHub Wiki

Effect

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.

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

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.

Inheritance

CustomDebugOutputConvertible, Publisher

Initializers

init(_:)

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

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

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: - publisher: A publisher.

init(value:)

Initializes an effect that immediately emits the value passed in.

public init(value: Output)

Parameters

  • value: - value: The value that is immediately emitted by the effect.

init(error:)

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

public init(error: Failure)

Parameters

  • error: - error: The error that is immediately emitted by the effect.

Properties

upstream

let upstream: AnyPublisher<Output, Failure>

none

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.

var none: Effect

debugOutput

var debugOutput: String

Methods

receive(subscriber:)

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

future(_:)

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

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

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

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

result(_:)

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

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

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

  • attemptToFulfill: - attemptToFulfill: A closure encapsulating some work to execute in the real world.

Returns

An effect.

run(_:)

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

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

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: - 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.

concatenate(_:)

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

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

Warning: Feedback filed: <https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58\>

Parameters

  • effects: - effects: A variadic list of effects.

Returns

A new effect

concatenate(_:)

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

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

Warning: Feedback filed: <https://gist.github.com/mbrandonw/611c8352e1bd1c22461bd505e320ab58\>

Parameters

  • effects: - effects: A collection of effects.

Returns

A new effect

merge(_:)

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

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

Parameters

  • effects: - effects: A list of effects.

Returns

A new effect

merge(_:)

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

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

Parameters

  • effects: - effects: A sequence of effects.

Returns

A new effect

fireAndForget(_:)

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

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

Parameters

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

Returns

An effect.

map(_:)

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

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

Parameters

  • transform: - transform: 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:cancelInFlight:)

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

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

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: - id: The effect's identifier.
  • cancelInFlight: - cancelInFlight: 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:)

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

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

Parameters

  • id: - id: An effect identifier.

Returns

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

debounce(id:for:scheduler:options:)

Turns an effect into one that can be debounced.

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

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)

Parameters

  • id: - id: The effect's identifier.
  • dueTime: - dueTime: The duration you want to debounce for.
  • scheduler: - scheduler: The scheduler you want to deliver the debounced output to.
  • options: - options: Scheduler options that customize the effect's delivery of elements.

Returns

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

async(_:)

@available(*, unavailable, renamed: "run") public static func async(_ work: @escaping (Effect.Subscriber) -> Cancellable) -> Self

throttle(id:for:scheduler:latest:)

Turns an effect into one that can be throttled.

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

Parameters

  • id: - id: The effect's identifier.
  • interval: - interval: The interval at which to find and emit the most recent element, expressed in the time system of the scheduler.
  • scheduler: - scheduler: The scheduler you want to deliver the throttled output to.
  • latest: - latest: 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.

⚠️ **GitHub.com Fallback** ⚠️