| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- import Foundation
- import enum Result.NoError
- /// Represents an action that will do some work when executed with a value of
- /// type `Input`, then return zero or more values of type `Output` and/or fail
- /// with an error of type `Error`. If no failure should be possible, NoError can
- /// be specified for the `Error` parameter.
- ///
- /// Actions enforce serial execution. Any attempt to execute an action multiple
- /// times concurrently will return an error.
- public final class Action<Input, Output, Error: ErrorType> {
- private let executeClosure: Input -> SignalProducer<Output, Error>
- private let eventsObserver: Signal<Event<Output, Error>, NoError>.Observer
- /// A signal of all events generated from applications of the Action.
- ///
- /// In other words, this will send every `Event` from every signal generated
- /// by each SignalProducer returned from apply().
- public let events: Signal<Event<Output, Error>, NoError>
- /// A signal of all values generated from applications of the Action.
- ///
- /// In other words, this will send every value from every signal generated
- /// by each SignalProducer returned from apply().
- public let values: Signal<Output, NoError>
- /// A signal of all errors generated from applications of the Action.
- ///
- /// In other words, this will send errors from every signal generated by
- /// each SignalProducer returned from apply().
- public let errors: Signal<Error, NoError>
- /// Whether the action is currently executing.
- public var executing: AnyProperty<Bool> {
- return AnyProperty(_executing)
- }
- private let _executing: MutableProperty<Bool> = MutableProperty(false)
- /// Whether the action is currently enabled.
- public var enabled: AnyProperty<Bool> {
- return AnyProperty(_enabled)
- }
- private let _enabled: MutableProperty<Bool> = MutableProperty(false)
- /// Whether the instantiator of this action wants it to be enabled.
- private let userEnabled: AnyProperty<Bool>
- /// This queue is used for read-modify-write operations on the `_executing`
- /// property.
- private let executingQueue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.Action.executingQueue", DISPATCH_QUEUE_SERIAL)
- /// Whether the action should be enabled for the given combination of user
- /// enabledness and executing status.
- private static func shouldBeEnabled(userEnabled userEnabled: Bool, executing: Bool) -> Bool {
- return userEnabled && !executing
- }
- /// Initializes an action that will be conditionally enabled, and creates a
- /// SignalProducer for each input.
- ///
- /// - parameters:
- /// - enabledIf: Boolean property that shows whether the action is
- /// enabled.
- /// - execute: A closure that returns the signal producer returned by
- /// calling `apply(Input)` on the action.
- public init<P: PropertyType where P.Value == Bool>(enabledIf: P, _ execute: Input -> SignalProducer<Output, Error>) {
- executeClosure = execute
- userEnabled = AnyProperty(enabledIf)
- (events, eventsObserver) = Signal<Event<Output, Error>, NoError>.pipe()
- values = events.map { $0.value }.ignoreNil()
- errors = events.map { $0.error }.ignoreNil()
- _enabled <~ enabledIf.producer
- .combineLatestWith(_executing.producer)
- .map(Action.shouldBeEnabled)
- }
- /// Initializes an action that will be enabled by default, and creates a
- /// SignalProducer for each input.
- ///
- /// - parameters:
- /// - execute: A closure that returns the signal producer returned by
- /// calling `apply(Input)` on the action.
- public convenience init(_ execute: Input -> SignalProducer<Output, Error>) {
- self.init(enabledIf: ConstantProperty(true), execute)
- }
- deinit {
- eventsObserver.sendCompleted()
- }
- /// Creates a SignalProducer that, when started, will execute the action
- /// with the given input, then forward the results upon the produced Signal.
- ///
- /// - note: If the action is disabled when the returned SignalProducer is
- /// started, the produced signal will send `ActionError.NotEnabled`,
- /// and nothing will be sent upon `values` or `errors` for that
- /// particular signal.
- ///
- /// - parameters:
- /// - input: A value that will be passed to the closure creating the signal
- /// producer.
- @warn_unused_result(message="Did you forget to call `start` on the producer?")
- public func apply(input: Input) -> SignalProducer<Output, ActionError<Error>> {
- return SignalProducer { observer, disposable in
- var startedExecuting = false
- dispatch_sync(self.executingQueue) {
- if self._enabled.value {
- self._executing.value = true
- startedExecuting = true
- }
- }
- if !startedExecuting {
- observer.sendFailed(.NotEnabled)
- return
- }
- self.executeClosure(input).startWithSignal { signal, signalDisposable in
- disposable.addDisposable(signalDisposable)
- signal.observe { event in
- observer.action(event.mapError(ActionError.ProducerError))
- self.eventsObserver.sendNext(event)
- }
- }
- disposable += {
- self._executing.value = false
- }
- }
- }
- }
- public protocol ActionType {
- /// The type of argument to apply the action to.
- associatedtype Input
- /// The type of values returned by the action.
- associatedtype Output
- /// The type of error when the action fails. If errors aren't possible then
- /// `NoError` can be used.
- associatedtype Error: ErrorType
- /// Whether the action is currently enabled.
- var enabled: AnyProperty<Bool> { get }
- /// Extracts an action from the receiver.
- var action: Action<Input, Output, Error> { get }
- /// Creates a SignalProducer that, when started, will execute the action
- /// with the given input, then forward the results upon the produced Signal.
- ///
- /// - note: If the action is disabled when the returned SignalProducer is
- /// started, the produced signal will send `ActionError.NotEnabled`,
- /// and nothing will be sent upon `values` or `errors` for that
- /// particular signal.
- ///
- /// - parameters:
- /// - input: A value that will be passed to the closure creating the signal
- /// producer.
- func apply(input: Input) -> SignalProducer<Output, ActionError<Error>>
- }
- extension Action: ActionType {
- public var action: Action {
- return self
- }
- }
- /// The type of error that can occur from Action.apply, where `Error` is the
- /// type of error that can be generated by the specific Action instance.
- public enum ActionError<Error: ErrorType>: ErrorType {
- /// The producer returned from apply() was started while the Action was
- /// disabled.
- case NotEnabled
- /// The producer returned from apply() sent the given error.
- case ProducerError(Error)
- }
- public func == <Error: Equatable>(lhs: ActionError<Error>, rhs: ActionError<Error>) -> Bool {
- switch (lhs, rhs) {
- case (.NotEnabled, .NotEnabled):
- return true
- case let (.ProducerError(left), .ProducerError(right)):
- return left == right
- default:
- return false
- }
- }
|