Action.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import Dispatch
  2. import Foundation
  3. import enum Result.NoError
  4. /// Represents an action that will do some work when executed with a value of
  5. /// type `Input`, then return zero or more values of type `Output` and/or fail
  6. /// with an error of type `Error`. If no failure should be possible, NoError can
  7. /// be specified for the `Error` parameter.
  8. ///
  9. /// Actions enforce serial execution. Any attempt to execute an action multiple
  10. /// times concurrently will return an error.
  11. public final class Action<Input, Output, Error: Swift.Error> {
  12. private let deinitToken: Lifetime.Token
  13. private let executeClosure: (_ state: Any, _ input: Input) -> SignalProducer<Output, Error>
  14. private let eventsObserver: Signal<Event<Output, Error>, NoError>.Observer
  15. private let disabledErrorsObserver: Signal<(), NoError>.Observer
  16. /// The lifetime of the Action.
  17. public let lifetime: Lifetime
  18. /// A signal of all events generated from applications of the Action.
  19. ///
  20. /// In other words, this will send every `Event` from every signal generated
  21. /// by each SignalProducer returned from apply() except `ActionError.disabled`.
  22. public let events: Signal<Event<Output, Error>, NoError>
  23. /// A signal of all values generated from applications of the Action.
  24. ///
  25. /// In other words, this will send every value from every signal generated
  26. /// by each SignalProducer returned from apply() except `ActionError.disabled`.
  27. public let values: Signal<Output, NoError>
  28. /// A signal of all errors generated from applications of the Action.
  29. ///
  30. /// In other words, this will send errors from every signal generated by
  31. /// each SignalProducer returned from apply() except `ActionError.disabled`.
  32. public let errors: Signal<Error, NoError>
  33. /// A signal which is triggered by `ActionError.disabled`.
  34. public let disabledErrors: Signal<(), NoError>
  35. /// A signal of all completed events generated from applications of the action.
  36. ///
  37. /// In other words, this will send completed events from every signal generated
  38. /// by each SignalProducer returned from apply().
  39. public let completed: Signal<(), NoError>
  40. /// Whether the action is currently executing.
  41. public let isExecuting: Property<Bool>
  42. /// Whether the action is currently enabled.
  43. public let isEnabled: Property<Bool>
  44. private let state: MutableProperty<ActionState>
  45. /// Initializes an action that will be conditionally enabled based on the
  46. /// value of `state`. Creates a `SignalProducer` for each input and the
  47. /// current value of `state`.
  48. ///
  49. /// - note: `Action` guarantees that changes to `state` are observed in a
  50. /// thread-safe way. Thus, the value passed to `isEnabled` will
  51. /// always be identical to the value passed to `execute`, for each
  52. /// application of the action.
  53. ///
  54. /// - note: This initializer should only be used if you need to provide
  55. /// custom input can also influence whether the action is enabled.
  56. /// The various convenience initializers should cover most use cases.
  57. ///
  58. /// - parameters:
  59. /// - state: A property that provides the current state of the action
  60. /// whenever `apply()` is called.
  61. /// - enabledIf: A predicate that, given the current value of `state`,
  62. /// returns whether the action should be enabled.
  63. /// - execute: A closure that returns the `SignalProducer` returned by
  64. /// calling `apply(Input)` on the action, optionally using
  65. /// the current value of `state`.
  66. public init<State: PropertyProtocol>(state property: State, enabledIf isEnabled: @escaping (State.Value) -> Bool, _ execute: @escaping (State.Value, Input) -> SignalProducer<Output, Error>) {
  67. deinitToken = Lifetime.Token()
  68. lifetime = Lifetime(deinitToken)
  69. // Retain the `property` for the created `Action`.
  70. lifetime.ended.observeCompleted { _ = property }
  71. executeClosure = { state, input in execute(state as! State.Value, input) }
  72. (events, eventsObserver) = Signal<Event<Output, Error>, NoError>.pipe()
  73. (disabledErrors, disabledErrorsObserver) = Signal<(), NoError>.pipe()
  74. values = events.map { $0.value }.skipNil()
  75. errors = events.map { $0.error }.skipNil()
  76. completed = events.filter { $0.isCompleted }.map { _ in }
  77. let initial = ActionState(value: property.value, isEnabled: { isEnabled($0 as! State.Value) })
  78. state = MutableProperty(initial)
  79. property.signal
  80. .take(during: state.lifetime)
  81. .observeValues { [weak state] newValue in
  82. state?.modify {
  83. $0.value = newValue
  84. }
  85. }
  86. self.isEnabled = state.map { $0.isEnabled }
  87. self.isExecuting = state.map { $0.isExecuting }
  88. }
  89. /// Initializes an action that will be conditionally enabled, and creates a
  90. /// `SignalProducer` for each input.
  91. ///
  92. /// - parameters:
  93. /// - enabledIf: Boolean property that shows whether the action is
  94. /// enabled.
  95. /// - execute: A closure that returns the signal producer returned by
  96. /// calling `apply(Input)` on the action.
  97. public convenience init<P: PropertyProtocol>(enabledIf property: P, _ execute: @escaping (Input) -> SignalProducer<Output, Error>) where P.Value == Bool {
  98. self.init(state: property, enabledIf: { $0 }) { _, input in
  99. execute(input)
  100. }
  101. }
  102. /// Initializes an action that will be enabled by default, and creates a
  103. /// SignalProducer for each input.
  104. ///
  105. /// - parameters:
  106. /// - execute: A closure that returns the signal producer returned by
  107. /// calling `apply(Input)` on the action.
  108. public convenience init(_ execute: @escaping (Input) -> SignalProducer<Output, Error>) {
  109. self.init(enabledIf: Property(value: true), execute)
  110. }
  111. deinit {
  112. eventsObserver.sendCompleted()
  113. disabledErrorsObserver.sendCompleted()
  114. }
  115. /// Creates a SignalProducer that, when started, will execute the action
  116. /// with the given input, then forward the results upon the produced Signal.
  117. ///
  118. /// - note: If the action is disabled when the returned SignalProducer is
  119. /// started, the produced signal will send `ActionError.disabled`,
  120. /// and nothing will be sent upon `values` or `errors` for that
  121. /// particular signal.
  122. ///
  123. /// - parameters:
  124. /// - input: A value that will be passed to the closure creating the signal
  125. /// producer.
  126. public func apply(_ input: Input) -> SignalProducer<Output, ActionError<Error>> {
  127. return SignalProducer { observer, disposable in
  128. let startingState = self.state.modify { state -> Any? in
  129. if state.isEnabled {
  130. state.isExecuting = true
  131. return state.value
  132. } else {
  133. return nil
  134. }
  135. }
  136. guard let state = startingState else {
  137. observer.send(error: .disabled)
  138. self.disabledErrorsObserver.send(value: ())
  139. return
  140. }
  141. self.executeClosure(state, input).startWithSignal { signal, signalDisposable in
  142. disposable += signalDisposable
  143. signal.observe { event in
  144. observer.action(event.mapError(ActionError.producerFailed))
  145. self.eventsObserver.send(value: event)
  146. }
  147. }
  148. disposable += {
  149. self.state.modify {
  150. $0.isExecuting = false
  151. }
  152. }
  153. }
  154. }
  155. }
  156. private struct ActionState {
  157. var isExecuting: Bool = false
  158. var value: Any {
  159. didSet {
  160. userEnabled = userEnabledClosure(value)
  161. }
  162. }
  163. private var userEnabled: Bool
  164. private let userEnabledClosure: (Any) -> Bool
  165. init(value: Any, isEnabled: @escaping (Any) -> Bool) {
  166. self.value = value
  167. self.userEnabled = isEnabled(value)
  168. self.userEnabledClosure = isEnabled
  169. }
  170. /// Whether the action should be enabled for the given combination of user
  171. /// enabledness and executing status.
  172. fileprivate var isEnabled: Bool {
  173. return userEnabled && !isExecuting
  174. }
  175. }
  176. /// A protocol used to constraint `Action` initializers.
  177. public protocol ActionProtocol: BindingTargetProtocol {
  178. /// The type of argument to apply the action to.
  179. associatedtype Input
  180. /// The type of values returned by the action.
  181. associatedtype Output
  182. /// The type of error when the action fails. If errors aren't possible then
  183. /// `NoError` can be used.
  184. associatedtype Error: Swift.Error
  185. /// Initializes an action that will be conditionally enabled based on the
  186. /// value of `state`. Creates a `SignalProducer` for each input and the
  187. /// current value of `state`.
  188. ///
  189. /// - note: `Action` guarantees that changes to `state` are observed in a
  190. /// thread-safe way. Thus, the value passed to `isEnabled` will
  191. /// always be identical to the value passed to `execute`, for each
  192. /// application of the action.
  193. ///
  194. /// - note: This initializer should only be used if you need to provide
  195. /// custom input can also influence whether the action is enabled.
  196. /// The various convenience initializers should cover most use cases.
  197. ///
  198. /// - parameters:
  199. /// - state: A property that provides the current state of the action
  200. /// whenever `apply()` is called.
  201. /// - enabledIf: A predicate that, given the current value of `state`,
  202. /// returns whether the action should be enabled.
  203. /// - execute: A closure that returns the `SignalProducer` returned by
  204. /// calling `apply(Input)` on the action, optionally using
  205. /// the current value of `state`.
  206. init<State: PropertyProtocol>(state property: State, enabledIf isEnabled: @escaping (State.Value) -> Bool, _ execute: @escaping (State.Value, Input) -> SignalProducer<Output, Error>)
  207. /// Whether the action is currently enabled.
  208. var isEnabled: Property<Bool> { get }
  209. /// Extracts an action from the receiver.
  210. var action: Action<Input, Output, Error> { get }
  211. /// Creates a SignalProducer that, when started, will execute the action
  212. /// with the given input, then forward the results upon the produced Signal.
  213. ///
  214. /// - note: If the action is disabled when the returned SignalProducer is
  215. /// started, the produced signal will send `ActionError.disabled`,
  216. /// and nothing will be sent upon `values` or `errors` for that
  217. /// particular signal.
  218. ///
  219. /// - parameters:
  220. /// - input: A value that will be passed to the closure creating the signal
  221. /// producer.
  222. func apply(_ input: Input) -> SignalProducer<Output, ActionError<Error>>
  223. }
  224. extension ActionProtocol {
  225. public func consume(_ value: Input) {
  226. apply(value).start()
  227. }
  228. }
  229. extension Action: ActionProtocol {
  230. public var action: Action {
  231. return self
  232. }
  233. }
  234. extension ActionProtocol where Input == Void {
  235. /// Initializes an action that uses an `Optional` property for its input,
  236. /// and is disabled whenever the input is `nil`. When executed, a `SignalProducer`
  237. /// is created with the current value of the input.
  238. ///
  239. /// - parameters:
  240. /// - input: An `Optional` property whose current value is used as input
  241. /// whenever the action is executed. The action is disabled
  242. /// whenever the value is `nil`.
  243. /// - execute: A closure to return a new `SignalProducer` based on the
  244. /// current value of `input`.
  245. public init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
  246. self.init(state: input, enabledIf: { $0 != nil }) { input, _ in
  247. execute(input!)
  248. }
  249. }
  250. /// Initializes an action that uses a property for its input. When executed,
  251. /// a `SignalProducer` is created with the current value of the input.
  252. ///
  253. /// - parameters:
  254. /// - input: A property whose current value is used as input
  255. /// whenever the action is executed.
  256. /// - execute: A closure to return a new `SignalProducer` based on the
  257. /// current value of `input`.
  258. public init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T {
  259. self.init(input: input.map(Optional.some), execute)
  260. }
  261. }
  262. /// The type of error that can occur from Action.apply, where `Error` is the
  263. /// type of error that can be generated by the specific Action instance.
  264. public enum ActionError<Error: Swift.Error>: Swift.Error {
  265. /// The producer returned from apply() was started while the Action was
  266. /// disabled.
  267. case disabled
  268. /// The producer returned from apply() sent the given error.
  269. case producerFailed(Error)
  270. }
  271. public func == <Error: Equatable>(lhs: ActionError<Error>, rhs: ActionError<Error>) -> Bool {
  272. switch (lhs, rhs) {
  273. case (.disabled, .disabled):
  274. return true
  275. case let (.producerFailed(left), .producerFailed(right)):
  276. return left == right
  277. default:
  278. return false
  279. }
  280. }