Skip to content

Commit

Permalink
Merge pull request #455 from ReactiveCocoa/unwrapping-action
Browse files Browse the repository at this point in the history
Revisit Action state-property convenience initialisers
  • Loading branch information
mdiep authored Jun 15, 2017
2 parents d6268d3 + 8813c1a commit bdd2d26
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# master
*Please add new entries at the top.*

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)

# 2.0.0-alpha.3
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)

Expand Down
43 changes: 39 additions & 4 deletions Sources/Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@ public final class Action<Input, Output, Error: Swift.Error> {
}
}

/// Initializes an `Action` that uses a property as its state.
///
/// When the `Action` is asked to start the execution, a unit of work — represented by
/// a `SignalProducer` — would be created by invoking `execute` with the latest value
/// of the state.
///
/// - parameters:
/// - state: A property to be the state of the `Action`.
/// - execute: A closure that produces a unit of work, as `SignalProducer`, to
/// be executed by the `Action`.
public convenience init<P: PropertyProtocol>(state: P, execute: @escaping (P.Value, Input) -> SignalProducer<Output, Error>) {
self.init(state: state, enabledIf: { _ in true }, execute: execute)
}

/// Initializes an `Action` that would be conditionally enabled.
///
/// When the `Action` is asked to start the execution with an input value, a unit of
Expand All @@ -184,6 +198,25 @@ public final class Action<Input, Output, Error: Swift.Error> {
}
}

/// Initializes an `Action` that uses a property of optional as its state.
///
/// When the `Action` is asked to start executing, a unit of work (represented by
/// a `SignalProducer`) is created by invoking `execute` with the latest value
/// of the state and the `input` that was passed to `apply()`.
///
/// If the property holds a `nil`, the `Action` would be disabled until it is not
/// `nil`.
///
/// - parameters:
/// - state: A property of optional to be the state of the `Action`.
/// - execute: A closure that produces a unit of work, as `SignalProducer`, to
/// be executed by the `Action`.
public convenience init<P: PropertyProtocol, T>(unwrapping state: P, execute: @escaping (T, Input) -> SignalProducer<Output, Error>) where P.Value == T? {
self.init(state: state, enabledIf: { $0 != nil }) { state, input in
execute(state!, input)
}
}

/// Initializes an `Action` that would always be enabled.
///
/// When the `Action` is asked to start the execution with an input value, a unit of
Expand Down Expand Up @@ -252,9 +285,9 @@ extension Action where Input == Void {
/// - state: A property of optional to be the state of the `Action`.
/// - execute: A closure that produces a unit of work, as `SignalProducer`, to
/// be executed by the `Action`.
public convenience init<P: PropertyProtocol, T>(state: P, execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
self.init(state: state, enabledIf: { $0 != nil }) { state, _ in
execute(state!)
public convenience init<P: PropertyProtocol, T>(unwrapping state: P, execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? {
self.init(unwrapping: state) { state, _ in
execute(state)
}
}

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

Expand Down
5 changes: 4 additions & 1 deletion Sources/Deprecations+Removals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ extension Action {
}

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

@available(*, unavailable, renamed:"init(unwrapping:execute:)")
public convenience init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T? { fatalError() }

@available(*, unavailable, renamed:"init(state:execute:)")
Expand Down
49 changes: 48 additions & 1 deletion Tests/ReactiveSwiftTests/ActionSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,61 @@ class ActionSpec: QuickSpec {
expect(values) == [1, 2, 3]
}

it("allows a non-void input type") {
let state = MutableProperty(1)

let add = Action<Int, Int, NoError>(state: state) { state, input in
SignalProducer(value: state + input)
}

var values: [Int] = []
add.values.observeValues { values.append($0) }

add.apply(2).start()
add.apply(3).start()

state.value = -1
add.apply(-10).start()

expect(values) == [3, 4, -11]
}

it("is disabled if the property is nil") {
let input = MutableProperty<Int?>(1)
let action = Action(state: input, execute: echo)
let action = Action(unwrapping: input, execute: echo)

expect(action.isEnabled.value) == true
input.value = nil
expect(action.isEnabled.value) == false
}

it("allows a different input type while unwrapping an optional state property") {
let state = MutableProperty<Int?>(nil)

let add = Action<String, Int?, NoError>(unwrapping: state) { state, input -> SignalProducer<Int?, NoError> in
guard let input = Int(input) else { return SignalProducer(value: nil) }
return SignalProducer(value: state + input)
}

var values: [Int] = []
add.values.observeValues { output in
if let output = output {
values.append(output)
}
}

expect(add.isEnabled.value) == false
state.value = 1
expect(add.isEnabled.value) == true

add.apply("2").start()
add.apply("3").start()

state.value = -1
add.apply("-10").start()

expect(values) == [3, 4, -11]
}
}
}
}

0 comments on commit bdd2d26

Please sign in to comment.