Action.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import Foundation
  2. import enum Result.NoError
  3. /// Represents an action that will do some work when executed with a value of
  4. /// type `Input`, then return zero or more values of type `Output` and/or fail
  5. /// with an error of type `Error`. If no failure should be possible, NoError can
  6. /// be specified for the `Error` parameter.
  7. ///
  8. /// Actions enforce serial execution. Any attempt to execute an action multiple
  9. /// times concurrently will return an error.
  10. public final class Action<Input, Output, Error: ErrorType> {
  11. private let executeClosure: Input -> SignalProducer<Output, Error>
  12. private let eventsObserver: Signal<Event<Output, Error>, NoError>.Observer
  13. /// A signal of all events generated from applications of the Action.
  14. ///
  15. /// In other words, this will send every `Event` from every signal generated
  16. /// by each SignalProducer returned from apply().
  17. public let events: Signal<Event<Output, Error>, NoError>
  18. /// A signal of all values generated from applications of the Action.
  19. ///
  20. /// In other words, this will send every value from every signal generated
  21. /// by each SignalProducer returned from apply().
  22. public let values: Signal<Output, NoError>
  23. /// A signal of all errors generated from applications of the Action.
  24. ///
  25. /// In other words, this will send errors from every signal generated by
  26. /// each SignalProducer returned from apply().
  27. public let errors: Signal<Error, NoError>
  28. /// Whether the action is currently executing.
  29. public var executing: AnyProperty<Bool> {
  30. return AnyProperty(_executing)
  31. }
  32. private let _executing: MutableProperty<Bool> = MutableProperty(false)
  33. /// Whether the action is currently enabled.
  34. public var enabled: AnyProperty<Bool> {
  35. return AnyProperty(_enabled)
  36. }
  37. private let _enabled: MutableProperty<Bool> = MutableProperty(false)
  38. /// Whether the instantiator of this action wants it to be enabled.
  39. private let userEnabled: AnyProperty<Bool>
  40. /// This queue is used for read-modify-write operations on the `_executing`
  41. /// property.
  42. private let executingQueue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.Action.executingQueue", DISPATCH_QUEUE_SERIAL)
  43. /// Whether the action should be enabled for the given combination of user
  44. /// enabledness and executing status.
  45. private static func shouldBeEnabled(userEnabled userEnabled: Bool, executing: Bool) -> Bool {
  46. return userEnabled && !executing
  47. }
  48. /// Initializes an action that will be conditionally enabled, and creates a
  49. /// SignalProducer for each input.
  50. ///
  51. /// - parameters:
  52. /// - enabledIf: Boolean property that shows whether the action is
  53. /// enabled.
  54. /// - execute: A closure that returns the signal producer returned by
  55. /// calling `apply(Input)` on the action.
  56. public init<P: PropertyType where P.Value == Bool>(enabledIf: P, _ execute: Input -> SignalProducer<Output, Error>) {
  57. executeClosure = execute
  58. userEnabled = AnyProperty(enabledIf)
  59. (events, eventsObserver) = Signal<Event<Output, Error>, NoError>.pipe()
  60. values = events.map { $0.value }.ignoreNil()
  61. errors = events.map { $0.error }.ignoreNil()
  62. _enabled <~ enabledIf.producer
  63. .combineLatestWith(_executing.producer)
  64. .map(Action.shouldBeEnabled)
  65. }
  66. /// Initializes an action that will be enabled by default, and creates a
  67. /// SignalProducer for each input.
  68. ///
  69. /// - parameters:
  70. /// - execute: A closure that returns the signal producer returned by
  71. /// calling `apply(Input)` on the action.
  72. public convenience init(_ execute: Input -> SignalProducer<Output, Error>) {
  73. self.init(enabledIf: ConstantProperty(true), execute)
  74. }
  75. deinit {
  76. eventsObserver.sendCompleted()
  77. }
  78. /// Creates a SignalProducer that, when started, will execute the action
  79. /// with the given input, then forward the results upon the produced Signal.
  80. ///
  81. /// - note: If the action is disabled when the returned SignalProducer is
  82. /// started, the produced signal will send `ActionError.NotEnabled`,
  83. /// and nothing will be sent upon `values` or `errors` for that
  84. /// particular signal.
  85. ///
  86. /// - parameters:
  87. /// - input: A value that will be passed to the closure creating the signal
  88. /// producer.
  89. @warn_unused_result(message="Did you forget to call `start` on the producer?")
  90. public func apply(input: Input) -> SignalProducer<Output, ActionError<Error>> {
  91. return SignalProducer { observer, disposable in
  92. var startedExecuting = false
  93. dispatch_sync(self.executingQueue) {
  94. if self._enabled.value {
  95. self._executing.value = true
  96. startedExecuting = true
  97. }
  98. }
  99. if !startedExecuting {
  100. observer.sendFailed(.NotEnabled)
  101. return
  102. }
  103. self.executeClosure(input).startWithSignal { signal, signalDisposable in
  104. disposable.addDisposable(signalDisposable)
  105. signal.observe { event in
  106. observer.action(event.mapError(ActionError.ProducerError))
  107. self.eventsObserver.sendNext(event)
  108. }
  109. }
  110. disposable += {
  111. self._executing.value = false
  112. }
  113. }
  114. }
  115. }
  116. public protocol ActionType {
  117. /// The type of argument to apply the action to.
  118. associatedtype Input
  119. /// The type of values returned by the action.
  120. associatedtype Output
  121. /// The type of error when the action fails. If errors aren't possible then
  122. /// `NoError` can be used.
  123. associatedtype Error: ErrorType
  124. /// Whether the action is currently enabled.
  125. var enabled: AnyProperty<Bool> { get }
  126. /// Extracts an action from the receiver.
  127. var action: Action<Input, Output, Error> { get }
  128. /// Creates a SignalProducer that, when started, will execute the action
  129. /// with the given input, then forward the results upon the produced Signal.
  130. ///
  131. /// - note: If the action is disabled when the returned SignalProducer is
  132. /// started, the produced signal will send `ActionError.NotEnabled`,
  133. /// and nothing will be sent upon `values` or `errors` for that
  134. /// particular signal.
  135. ///
  136. /// - parameters:
  137. /// - input: A value that will be passed to the closure creating the signal
  138. /// producer.
  139. func apply(input: Input) -> SignalProducer<Output, ActionError<Error>>
  140. }
  141. extension Action: ActionType {
  142. public var action: Action {
  143. return self
  144. }
  145. }
  146. /// The type of error that can occur from Action.apply, where `Error` is the
  147. /// type of error that can be generated by the specific Action instance.
  148. public enum ActionError<Error: ErrorType>: ErrorType {
  149. /// The producer returned from apply() was started while the Action was
  150. /// disabled.
  151. case NotEnabled
  152. /// The producer returned from apply() sent the given error.
  153. case ProducerError(Error)
  154. }
  155. public func == <Error: Equatable>(lhs: ActionError<Error>, rhs: ActionError<Error>) -> Bool {
  156. switch (lhs, rhs) {
  157. case (.NotEnabled, .NotEnabled):
  158. return true
  159. case let (.ProducerError(left), .ProducerError(right)):
  160. return left == right
  161. default:
  162. return false
  163. }
  164. }