Observables, drivers and the rest of the observable types
In most instances we want to ditch observable
from our object names and treat these in the following way:
- Input that we get from the VC -
events
- Output that we send into the VC from the presenter -
actions
Not Preferred:
let buttonTapObservable: Observable<Void>
let itemsObservable: Observable<Void>
Preferred:
let buttonTap: Observable<Void>
let items: Observable<Void>
Relays and subjects
An exception to this are Relays
and Subjects
. Since they aren't only meant to be read and can accept values, keeping Relay
and Subject
in the name helps to immediatelly recognize their context.
let modelUpdateSubject = BehaviorSubject<Model?>(value: nil)
let formEditsRelay = BehaviorRelay<Model>(value: model)
Actions grouping
Actions which can be grouped should be organized as such. For instance, we've mentioned events
and actions
that are sent between our module components.
In instances where we can do that, a simple struct
wrapper works wonders to help you out in organizing the code, if ever so slightly.
An example of that would look like the following:
struct Actions {
save: Observable<Void>,
cancel: Observable<Void>,
...
}
struct Events {
...
}
We declare these in our namespace struct
in order to not pollute the rest of the project. Afterwards, simply use them in our default I/O approach during the initial binding:
struct ViewOutput {
let actions: Actions
}
struct ViewInput {
let events: Events
let items: Observable<Item>
}
Note: In cases where it makes no sense to group actions, e.g.,
let items: Observable<Item>
in the above example, it is only important to follow the very first part of the naming section. That includes deciding on a property name, while omitting the observable type from the name.
Presenters' bind function
We should strive towards the exact same name for the bind function, along with its parameters. Please use the following in your projects:
presenter.configure(with output: Module.ViewOutput) -> Module.ViewInput
Handle functions
Considering that all of the actions we need to handle will end up in the given configure
function, we'll need to format the code appropriately so that we don't end up with a "Massive View Controller" configure function.
The general template we follow is to use handle
functions, which will transform our data and then either subscribe or return the value. Example:
func configure(with output: Module.ViewOutput) -> Module.ViewInput {
_handle(action: output.action)
let result = _handle(action: output.action)
return Module.ViewInput(
result: result
)
}
Depending on the action name and whether it conflicts with an already given name, for instance _handle(viewDidLoad: output.viewDidLoad)
, we can make use of labels to further identify what are we going to do in the handle
function. In those instances, we would use the viewActionWith label.