Skip to content

Commit f4a3a92

Browse files
committed
Action documentation rewrite and initializer renaming.
1 parent a323ced commit f4a3a92

File tree

3 files changed

+102
-77
lines changed

3 files changed

+102
-77
lines changed

Sources/Action.swift

Lines changed: 88 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,44 @@ import Dispatch
22
import Foundation
33
import Result
44

5-
/// Represents an action that will do some work when executed with a value of
6-
/// type `Input`, then return zero or more values of type `Output` and/or fail
7-
/// with an error of type `Error`. If no failure should be possible, NoError can
8-
/// be specified for the `Error` parameter.
5+
/// `Action` represents a repeatable work with varying input and state that may output
6+
/// zero or more values, or fail with an error at some point.
97
///
10-
/// Actions enforce serial execution. Any attempt to execute an action multiple
11-
/// times concurrently will return an error.
8+
/// The core of `Action` is the `workProducer` closure that is supplied to the
9+
/// initializer. For every execution attempt with a varying input, if the `Action` is
10+
/// enabled, it would invoke `workProducer` with the latest state and the input to obtain
11+
/// a customized unit of work — represented by a `SignalProducer`.
12+
///
13+
/// `Action` enforces serial execution, and disables the `Action` during the execution.
1214
public final class Action<Input, Output, Error: Swift.Error> {
1315
private let deinitToken: Lifetime.Token
1416

1517
private let executeClosure: (_ state: Any, _ input: Input) -> SignalProducer<Output, Error>
1618
private let eventsObserver: Signal<Event<Output, Error>, NoError>.Observer
1719
private let disabledErrorsObserver: Signal<(), NoError>.Observer
1820

19-
/// The lifetime of the Action.
21+
/// The lifetime of the `Action`.
2022
public let lifetime: Lifetime
2123

22-
/// A signal of all events generated from applications of the Action.
24+
/// A signal of all events generated from all units of work of the `Action`.
2325
///
24-
/// In other words, this will send every `Event` from every signal generated
25-
/// by each SignalProducer returned from apply() except `ActionError.disabled`.
26+
/// In other words, this sends every `Event` from every unit of work that the `Action`
27+
/// executes.
2628
public let events: Signal<Event<Output, Error>, NoError>
2729

28-
/// A signal of all values generated from applications of the Action.
30+
/// A signal of all values generated from all units of work of the `Action`.
2931
///
30-
/// In other words, this will send every value from every signal generated
31-
/// by each SignalProducer returned from apply() except `ActionError.disabled`.
32+
/// In other words, this sends every value from every unit of work that the `Action`
33+
/// executes.
3234
public let values: Signal<Output, NoError>
3335

34-
/// A signal of all errors generated from applications of the Action.
36+
/// A signal of all errors generated from all units of work of the `Action`.
3537
///
36-
/// In other words, this will send errors from every signal generated by
37-
/// each SignalProducer returned from apply() except `ActionError.disabled`.
38+
/// In other words, this sends every error from every unit of work that the `Action`
39+
/// executes.
3840
public let errors: Signal<Error, NoError>
3941

40-
/// A signal which is triggered by `ActionError.disabled`.
42+
/// A signal of all failed attempts to start a unit of work of the `Action`.
4143
public let disabledErrors: Signal<(), NoError>
4244

4345
/// A signal of all completed events generated from applications of the action.
@@ -54,9 +56,12 @@ public final class Action<Input, Output, Error: Swift.Error> {
5456

5557
private let state: MutableProperty<ActionState>
5658

57-
/// Initializes an action that will be conditionally enabled based on the
58-
/// value of `state`. Creates a `SignalProducer` for each input and the
59-
/// current value of `state`.
59+
/// Initializes an `Action` that would be conditionally enabled depending on its
60+
/// state.
61+
///
62+
/// When the `Action` is asked to start the execution with an input value, a unit of
63+
/// work — represented by a `SignalProducer` — would be created by invoking
64+
/// `workProducer` with the latest state and the input value.
6065
///
6166
/// - note: `Action` guarantees that changes to `state` are observed in a
6267
/// thread-safe way. Thus, the value passed to `isEnabled` will
@@ -68,21 +73,19 @@ public final class Action<Input, Output, Error: Swift.Error> {
6873
/// The various convenience initializers should cover most use cases.
6974
///
7075
/// - parameters:
71-
/// - state: A property that provides the current state of the action
72-
/// whenever `apply()` is called.
73-
/// - enabledIf: A predicate that, given the current value of `state`,
74-
/// returns whether the action should be enabled.
75-
/// - execute: A closure that returns the `SignalProducer` returned by
76-
/// calling `apply(Input)` on the action, optionally using
77-
/// the current value of `state`.
78-
public init<State: PropertyProtocol>(state property: State, enabledIf isEnabled: @escaping (State.Value) -> Bool, _ execute: @escaping (State.Value, Input) -> SignalProducer<Output, Error>) {
76+
/// - state: A property to be the state of the `Action`.
77+
/// - enabledIf: A predicate which determines the availability of the `Action`,
78+
/// given the latest `Action` state.
79+
/// - workProducer: A closure that produces a unit of work, as `SignalProducer`, to
80+
/// be executed by the `Action`.
81+
public init<State: PropertyProtocol>(state property: State, enabledIf isEnabled: @escaping (State.Value) -> Bool, _ workProducer: @escaping (State.Value, Input) -> SignalProducer<Output, Error>) {
7982
deinitToken = Lifetime.Token()
8083
lifetime = Lifetime(deinitToken)
8184

8285
// Retain the `property` for the created `Action`.
8386
lifetime.observeEnded { _ = property }
8487

85-
executeClosure = { state, input in execute(state as! State.Value, input) }
88+
executeClosure = { state, input in workProducer(state as! State.Value, input) }
8689

8790
(events, eventsObserver) = Signal<Event<Output, Error>, NoError>.pipe()
8891
(disabledErrors, disabledErrorsObserver) = Signal<(), NoError>.pipe()
@@ -106,46 +109,52 @@ public final class Action<Input, Output, Error: Swift.Error> {
106109
self.isExecuting = state.map { $0.isExecuting }.skipRepeats()
107110
}
108111

109-
/// Initializes an action that will be conditionally enabled, and creates a
110-
/// `SignalProducer` for each input.
112+
/// Initializes an `Action` that would be conditionally enabled.
113+
///
114+
/// When the `Action` is asked to start the execution with an input value, a unit of
115+
/// work — represented by a `SignalProducer` — would be created by invoking
116+
/// `workProducer` with the input value.
111117
///
112118
/// - parameters:
113-
/// - enabledIf: Boolean property that shows whether the action is
114-
/// enabled.
115-
/// - execute: A closure that returns the signal producer returned by
116-
/// calling `apply(Input)` on the action.
117-
public convenience init<P: PropertyProtocol>(enabledIf property: P, _ execute: @escaping (Input) -> SignalProducer<Output, Error>) where P.Value == Bool {
119+
/// - enabledIf: A property which determines the availability of the `Action`.
120+
/// - workProducer: A closure that produces a unit of work, as `SignalProducer`, to
121+
/// be executed by the `Action`.
122+
public convenience init<P: PropertyProtocol>(enabledIf property: P, _ workProducer: @escaping (Input) -> SignalProducer<Output, Error>) where P.Value == Bool {
118123
self.init(state: property, enabledIf: { $0 }) { _, input in
119-
execute(input)
124+
workProducer(input)
120125
}
121126
}
122127

123-
/// Initializes an action that will be enabled by default, and creates a
124-
/// SignalProducer for each input.
128+
/// Initializes an `Action` that would always be enabled.
129+
///
130+
/// When the `Action` is asked to start the execution with an input value, a unit of
131+
/// work — represented by a `SignalProducer` — would be created by invoking
132+
/// `workProducer` with the input value.
125133
///
126134
/// - parameters:
127-
/// - execute: A closure that returns the signal producer returned by
128-
/// calling `apply(Input)` on the action.
129-
public convenience init(_ execute: @escaping (Input) -> SignalProducer<Output, Error>) {
130-
self.init(enabledIf: Property(value: true), execute)
135+
/// - workProducer: A closure that produces a unit of work, as `SignalProducer`, to
136+
/// be executed by the `Action`.
137+
public convenience init(_ workProducer: @escaping (Input) -> SignalProducer<Output, Error>) {
138+
self.init(enabledIf: Property(value: true), workProducer)
131139
}
132140

133141
deinit {
134142
eventsObserver.sendCompleted()
135143
disabledErrorsObserver.sendCompleted()
136144
}
137145

138-
/// Creates a SignalProducer that, when started, will execute the action
139-
/// with the given input, then forward the results upon the produced Signal.
146+
/// Create a `SignalProducer` that would attempt to create and start a unit of work of
147+
/// the `Action`. The `SignalProducer` would forward only events generated by the unit
148+
/// of work it created.
140149
///
141-
/// - note: If the action is disabled when the returned SignalProducer is
142-
/// started, the produced signal will send `ActionError.disabled`,
143-
/// and nothing will be sent upon `values` or `errors` for that
144-
/// particular signal.
150+
/// If the execution attempt is failed, the producer would fail with
151+
/// `ActionError.disabled`.
145152
///
146153
/// - parameters:
147-
/// - input: A value that will be passed to the closure creating the signal
148-
/// producer.
154+
/// - input: A value to be used to create the unit of work.
155+
///
156+
/// - returns: A producer that forwards events generated by its started unit of work,
157+
/// or emits `ActionError.disabled` if the execution attempt is failed.
149158
public func apply(_ input: Input) -> SignalProducer<Output, ActionError<Error>> {
150159
return SignalProducer { observer, disposable in
151160
let startingState = self.state.modify { state -> Any? in
@@ -213,43 +222,47 @@ extension Action: BindingTargetProvider {
213222
}
214223

215224
extension Action where Input == Void {
216-
/// Initializes an action that uses an `Optional` property for its input,
217-
/// and is disabled whenever the input is `nil`. When executed, a `SignalProducer`
218-
/// is created with the current value of the input.
225+
/// Initializes an `Action` that uses a property of optional as its state.
226+
///
227+
/// When the `Action` is asked to start the execution, a unit of work — represented by
228+
/// a `SignalProducer` — would be created by invoking `workProducer` with the latest
229+
/// value of the state.
230+
///
231+
/// If the property holds a `nil`, the `Action` would be disabled until it is not
232+
/// `nil`.
219233
///
220234
/// - parameters:
221-
/// - input: An `Optional` property whose current value is used as input
222-
/// whenever the action is executed. The action is disabled
223-
/// whenever the value is `nil`.
224-
/// - execute: A closure to return a new `SignalProducer` based on the
225-
/// current value of `input`.
226-
public convenience init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
227-
self.init(state: input, enabledIf: { $0 != nil }) { input, _ in
228-
execute(input!)
235+
/// - state: A property of optional to be the state of the `Action`.
236+
/// - workProducer: A closure that produces a unit of work, as `SignalProducer`, to
237+
/// be executed by the `Action`.
238+
public convenience init<P: PropertyProtocol, T>(state: P, _ workProducer: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
239+
self.init(state: state, enabledIf: { $0 != nil }) { state, _ in
240+
workProducer(state!)
229241
}
230242
}
231243

232-
/// Initializes an action that uses a property for its input. When executed,
233-
/// a `SignalProducer` is created with the current value of the input.
244+
/// Initializes an `Action` that uses a property as its state.
245+
///
246+
/// When the `Action` is asked to start the execution, a unit of work — represented by
247+
/// a `SignalProducer` — would be created by invoking `workProducer` with the latest
248+
/// value of the state.
234249
///
235250
/// - parameters:
236-
/// - input: A property whose current value is used as input
237-
/// whenever the action is executed.
238-
/// - execute: A closure to return a new `SignalProducer` based on the
239-
/// current value of `input`.
240-
public convenience init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T {
241-
self.init(input: input.map(Optional.some), execute)
251+
/// - state: A property to be the state of the `Action`.
252+
/// - workProducer: A closure that produces a unit of work, as `SignalProducer`, to
253+
/// be executed by the `Action`.
254+
public convenience init<P: PropertyProtocol, T>(state: P, _ workProducer: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T {
255+
self.init(state: state.map(Optional.some), workProducer)
242256
}
243257
}
244258

245-
/// The type of error that can occur from Action.apply, where `Error` is the
246-
/// type of error that can be generated by the specific Action instance.
259+
/// `ActionError` represents the error that could be emitted by a unit of work of a
260+
/// certain `Action`.
247261
public enum ActionError<Error: Swift.Error>: Swift.Error {
248-
/// The producer returned from apply() was started while the Action was
249-
/// disabled.
262+
/// The execution attempt was failed, since the `Action` was disabled.
250263
case disabled
251264

252-
/// The producer returned from apply() sent the given error.
265+
/// The unit of work emitted an error.
253266
case producerFailed(Error)
254267
}
255268

Sources/Deprecations+Removals.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ import Foundation
22
import enum Result.NoError
33

44
// MARK: Obsolete types in ReactiveSwift 2.0.
5+
extension Action where Input == Void {
6+
@available(*, deprecated, renamed:"init(state:_:)")
7+
public convenience init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
8+
self.init(state: input, execute)
9+
}
10+
11+
@available(*, deprecated, renamed:"init(state:_:)")
12+
public convenience init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T {
13+
self.init(state: input, execute)
14+
}
15+
}
16+
517
@available(*, unavailable, message: "This protocol has been removed. Constrain `Action` directly instead.")
618
public protocol ActionProtocol {}
719

Tests/ReactiveSwiftTests/ActionSpec.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ class ActionSpec: QuickSpec {
301301

302302
it("executes the action with the property's current value") {
303303
let input = MutableProperty(0)
304-
let action = Action(input: input, echo)
304+
let action = Action(state: input, echo)
305305

306306
var values: [Int] = []
307307
action.values.observeValues { values.append($0) }
@@ -318,7 +318,7 @@ class ActionSpec: QuickSpec {
318318

319319
it("is disabled if the property is nil") {
320320
let input = MutableProperty<Int?>(1)
321-
let action = Action(input: input, echo)
321+
let action = Action(state: input, echo)
322322

323323
expect(action.isEnabled.value) == true
324324
input.value = nil

0 commit comments

Comments
 (0)