Skip to content

Commit bdd2d26

Browse files
authored
Merge pull request #455 from ReactiveCocoa/unwrapping-action
Revisit Action state-property convenience initialisers
2 parents d6268d3 + 8813c1a commit bdd2d26

File tree

4 files changed

+93
-6
lines changed

4 files changed

+93
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# master
22
*Please add new entries at the top.*
33

4+
1. Added new convenience initialisers to `Action` that make creating actions with state input properties easier. When creating an `Action` that is conditionally enabled based on an optional property, use the renamed `Action.init(unwrapping:execute:)` initialisers. (#455, kudos to @sharplet)
5+
46
# 2.0.0-alpha.3
57
1. `combinePrevious` for `Signal` and `SignalProducer` no longer requires an initial value. The first tuple would be emitted as soon as the second value is received by the operator if no initial value is given. (#445, kudos to @andersio)
68

Sources/Action.swift

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,20 @@ public final class Action<Input, Output, Error: Swift.Error> {
168168
}
169169
}
170170

171+
/// Initializes an `Action` that uses a property as its state.
172+
///
173+
/// When the `Action` is asked to start the execution, a unit of work — represented by
174+
/// a `SignalProducer` — would be created by invoking `execute` with the latest value
175+
/// of the state.
176+
///
177+
/// - parameters:
178+
/// - state: A property to be the state of the `Action`.
179+
/// - execute: A closure that produces a unit of work, as `SignalProducer`, to
180+
/// be executed by the `Action`.
181+
public convenience init<P: PropertyProtocol>(state: P, execute: @escaping (P.Value, Input) -> SignalProducer<Output, Error>) {
182+
self.init(state: state, enabledIf: { _ in true }, execute: execute)
183+
}
184+
171185
/// Initializes an `Action` that would be conditionally enabled.
172186
///
173187
/// When the `Action` is asked to start the execution with an input value, a unit of
@@ -184,6 +198,25 @@ public final class Action<Input, Output, Error: Swift.Error> {
184198
}
185199
}
186200

201+
/// Initializes an `Action` that uses a property of optional as its state.
202+
///
203+
/// When the `Action` is asked to start executing, a unit of work (represented by
204+
/// a `SignalProducer`) is created by invoking `execute` with the latest value
205+
/// of the state and the `input` that was passed to `apply()`.
206+
///
207+
/// If the property holds a `nil`, the `Action` would be disabled until it is not
208+
/// `nil`.
209+
///
210+
/// - parameters:
211+
/// - state: A property of optional to be the state of the `Action`.
212+
/// - execute: A closure that produces a unit of work, as `SignalProducer`, to
213+
/// be executed by the `Action`.
214+
public convenience init<P: PropertyProtocol, T>(unwrapping state: P, execute: @escaping (T, Input) -> SignalProducer<Output, Error>) where P.Value == T? {
215+
self.init(state: state, enabledIf: { $0 != nil }) { state, input in
216+
execute(state!, input)
217+
}
218+
}
219+
187220
/// Initializes an `Action` that would always be enabled.
188221
///
189222
/// When the `Action` is asked to start the execution with an input value, a unit of
@@ -252,9 +285,9 @@ extension Action where Input == Void {
252285
/// - state: A property of optional to be the state of the `Action`.
253286
/// - execute: A closure that produces a unit of work, as `SignalProducer`, to
254287
/// be executed by the `Action`.
255-
public convenience init<P: PropertyProtocol, T>(state: P, execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
256-
self.init(state: state, enabledIf: { $0 != nil }) { state, _ in
257-
execute(state!)
288+
public convenience init<P: PropertyProtocol, T>(unwrapping state: P, execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
289+
self.init(unwrapping: state) { state, _ in
290+
execute(state)
258291
}
259292
}
260293

@@ -269,7 +302,9 @@ extension Action where Input == Void {
269302
/// - execute: A closure that produces a unit of work, as `SignalProducer`, to
270303
/// be executed by the `Action`.
271304
public convenience init<P: PropertyProtocol, T>(state: P, execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T {
272-
self.init(state: state.map(Optional.some), execute: execute)
305+
self.init(state: state) { state, _ in
306+
execute(state)
307+
}
273308
}
274309
}
275310

Sources/Deprecations+Removals.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ extension Action {
141141
}
142142

143143
extension Action where Input == Void {
144-
@available(*, unavailable, renamed:"init(state:execute:)")
144+
@available(*, unavailable, renamed:"init(unwrapping:execute:)")
145+
public convenience init<P: PropertyProtocol, T>(state: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? { fatalError() }
146+
147+
@available(*, unavailable, renamed:"init(unwrapping:execute:)")
145148
public convenience init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? { fatalError() }
146149

147150
@available(*, unavailable, renamed:"init(state:execute:)")

Tests/ReactiveSwiftTests/ActionSpec.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,14 +323,61 @@ class ActionSpec: QuickSpec {
323323
expect(values) == [1, 2, 3]
324324
}
325325

326+
it("allows a non-void input type") {
327+
let state = MutableProperty(1)
328+
329+
let add = Action<Int, Int, NoError>(state: state) { state, input in
330+
SignalProducer(value: state + input)
331+
}
332+
333+
var values: [Int] = []
334+
add.values.observeValues { values.append($0) }
335+
336+
add.apply(2).start()
337+
add.apply(3).start()
338+
339+
state.value = -1
340+
add.apply(-10).start()
341+
342+
expect(values) == [3, 4, -11]
343+
}
344+
326345
it("is disabled if the property is nil") {
327346
let input = MutableProperty<Int?>(1)
328-
let action = Action(state: input, execute: echo)
347+
let action = Action(unwrapping: input, execute: echo)
329348

330349
expect(action.isEnabled.value) == true
331350
input.value = nil
332351
expect(action.isEnabled.value) == false
333352
}
353+
354+
it("allows a different input type while unwrapping an optional state property") {
355+
let state = MutableProperty<Int?>(nil)
356+
357+
let add = Action<String, Int?, NoError>(unwrapping: state) { state, input -> SignalProducer<Int?, NoError> in
358+
guard let input = Int(input) else { return SignalProducer(value: nil) }
359+
return SignalProducer(value: state + input)
360+
}
361+
362+
var values: [Int] = []
363+
add.values.observeValues { output in
364+
if let output = output {
365+
values.append(output)
366+
}
367+
}
368+
369+
expect(add.isEnabled.value) == false
370+
state.value = 1
371+
expect(add.isEnabled.value) == true
372+
373+
add.apply("2").start()
374+
add.apply("3").start()
375+
376+
state.value = -1
377+
add.apply("-10").start()
378+
379+
expect(values) == [3, 4, -11]
380+
}
334381
}
335382
}
336383
}

0 commit comments

Comments
 (0)