For a command that initiates a network request when:
- The network connection is up (precondition)
- The network request is not already in progress (enforced serialization)
The lifecycle proceeds as follows:
- The view model exposes the command and describes its behavior
- The view binds the command to a control
- The control will automatically be enabled when the command's preconditions (above) pass
- When the control is clicked, the command is executed
- When results arrive from the network, the view model handles them
- The view model eventually fires some property or notification to update the view
Setup:
VM (creates command) ---> (passed to) View ---> (passed to) Control
Execution:
Control ---> Command ---> VM ---> View
- The view doesn't need to know when the command is valid, since the control is automatically enabled/disabled via its
rac_command
extension - The command can be bound to multiple input sources in exactly the same way
- The command can be directly executed while preserving all of its guarantees
- The view doesn't need to know anything about the behavior that will occur, since it does not directly receive the results of the command
- The view can present errors (via
RACCommand.errors
) without caring why or how they occurred - Signals are automatically delivered to the main thread, which helps avoid bugs in UI code
- Receiving results is confusing, because signals of signals (like
executionSignals
) are confusing - Views have access to more information about the command than they need, like
executionSignals
,allowsConcurrentExecution
, etc. - The relationship between the
signalBlock
and execution is unclear - The view model should respond to the view out-of-band (this is simpler, but occasionally makes things harder as well)
My biggest reason from staying away from
RACCommand
has been the ability to pend subscription to the execution signals, rather than erroring if the command is disabled.For example, I have a video player that will occasionally misbehave if it is sent play and pause commands when it is in an invalid state from the Application suspending then resuming again. I can have a signal that represents the enabledness of any player action such as a
play
action, defined in terms of the player being in an invalid state.RACCommand
will not defer subscription to the execution signal if it is disabled. I want to enqueue aplay
as soon as the app resumes, but not haveplay
called on the player itself until it becomes enabled again. The implementation is pretty simple, setting an enqueue state & enabledness on properties, then usingRACObserve
andcombineLatest:
to send a value when the a 'command' is enqueued and enabled. I'm using this instead offlatten
,switch
and its friends because enqueuing apause
should cancel any previously enqueuedplay
, and I'd like to subscribe to each of the player commands individually.This may not be the problem that
RACCommand
is trying to solve and may not be relevant to the MVVM use case.