Skip to content

Commit

Permalink
Make changes to Action's isExecuting and isEnabled atomic
Browse files Browse the repository at this point in the history
This unifies isExecuting and userEnabled in a single struct, so that
changes to either value can be made atomically.

Changes no longer need to be synchronised by a serial dispatch queue,
and atomically enforcing the enabledness of the action is much simpler.
  • Loading branch information
sharplet committed Oct 30, 2016
1 parent 3a4a1f2 commit efa93c6
Showing 1 changed file with 34 additions and 34 deletions.
68 changes: 34 additions & 34 deletions Sources/Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,10 @@ public final class Action<Input, Output, Error: Swift.Error> {
/// Whether the action is currently executing.
public let isExecuting: Property<Bool>

private let _isExecuting: MutableProperty<Bool> = MutableProperty(false)

/// Whether the action is currently enabled.
public var isEnabled: Property<Bool>

private let _isEnabled: MutableProperty<Bool> = MutableProperty(false)

/// Whether the instantiator of this action wants it to be enabled.
private let isUserEnabled: Property<Bool>
public let isEnabled: Property<Bool>

/// This queue is used for read-modify-write operations on the `_executing`
/// property.
private let executingQueue = DispatchQueue(
label: "org.reactivecocoa.ReactiveSwift.Action.executingQueue",
attributes: []
)

/// Whether the action should be enabled for the given combination of user
/// enabledness and executing status.
private static func shouldBeEnabled(userEnabled: Bool, executing: Bool) -> Bool {
return userEnabled && !executing
}
private let state: MutableProperty<ActionState>

/// Initializes an action that will be conditionally enabled, and creates a
/// SignalProducer for each input.
Expand All @@ -79,20 +61,25 @@ public final class Action<Input, Output, Error: Swift.Error> {
lifetime = Lifetime(deinitToken)

executeClosure = execute
isUserEnabled = Property(property)

(events, eventsObserver) = Signal<Event<Output, Error>, NoError>.pipe()
(disabledErrors, disabledErrorsObserver) = Signal<(), NoError>.pipe()

values = events.map { $0.value }.skipNil()
errors = events.map { $0.error }.skipNil()

isEnabled = Property(_isEnabled)
isExecuting = Property(_isExecuting)
state = MutableProperty(ActionState(isExecuting: false, userEnabled: property.value))

property.signal
.take(during: state.lifetime)
.observeValues { [weak state] isEnabled in
state?.modify {
$0.userEnabled = isEnabled
}
}

_isEnabled <~ property.producer
.combineLatest(with: isExecuting.producer)
.map(Action.shouldBeEnabled)
isEnabled = state.map { $0.isEnabled }
isExecuting = state.map { $0.isExecuting }
}

/// Initializes an action that will be enabled by default, and creates a
Expand Down Expand Up @@ -123,16 +110,16 @@ public final class Action<Input, Output, Error: Swift.Error> {
/// producer.
public func apply(_ input: Input) -> SignalProducer<Output, ActionError<Error>> {
return SignalProducer { observer, disposable in
var startedExecuting = false

self.executingQueue.sync {
if self._isEnabled.value {
self._isExecuting.value = true
startedExecuting = true
let startedExecuting = self.state.modify { state -> Bool in
if state.isEnabled {
state.isExecuting = true
return true
} else {
return false
}
}

if !startedExecuting {
guard startedExecuting else {
observer.send(error: .disabled)
self.disabledErrorsObserver.send(value: ())
return
Expand All @@ -148,12 +135,25 @@ public final class Action<Input, Output, Error: Swift.Error> {
}

disposable += {
self._isExecuting.value = false
self.state.modify {
$0.isExecuting = false
}
}
}
}
}

private struct ActionState {
var isExecuting: Bool
var userEnabled: Bool

/// Whether the action should be enabled for the given combination of user
/// enabledness and executing status.
fileprivate var isEnabled: Bool {
return userEnabled && !isExecuting
}
}

public protocol ActionProtocol: BindingTargetProtocol {
/// The type of argument to apply the action to.
associatedtype Input
Expand Down

0 comments on commit efa93c6

Please sign in to comment.