forked from ReactiveCocoa/ReactiveCocoa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Action.swift
176 lines (143 loc) · 6.01 KB
/
Action.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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 create a
/// SignalProducer for each input.
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 create a
/// SignalProducer for each input.
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.
///
/// 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.
@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.
///
/// 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.
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
}
}