A project living under the RxSwiftCommunity organization, Action is an important building block for reactive applications. Thinking about what actions are in your code, the definition is along the lines of:
A trigger event signals that it’s time to do something.
A task is performed.
Immediately, later (or maybe never!), some value results from performing this task.
Notice a pattern? The trigger event can be represented as an observable sequence of something, such as button taps, timer ticks, or gestures, which may or may not convey data, but always signals work to be done. The result of each action can therefore be seen as a sequence of results, one result for each piece of work performed.
In the middle sits the Action object. It does the following:
Provides an inputs observer to bind observable sequences to. You can also manually trigger new work.
Can observe an Observable<Bool> to determine its “enabled” status (in addition to whether it’s currently executing).
Calls your factory closure which performs / starts the work and returns an observable of results.
Exposes an elements observable sequence of all work results (a flatMap of all work observables).
Gracefully handles errors emitted by work observables.
Action exposes observables for errors, the current execution status, an observable of each work observable, guarantees that no new work starts when the previous has not completed, and it’s generally such a cool class that you don’t want to miss it!
Last but not least, Action defines a contract, where you provide some (or no) data, then some work is done and you may later get resulting data. How this contract is implemented doesn’t matter to the code using the action. You can replace real actions with mock ones for testing without impacting the code at all, as long as the mock respects the contract.
Creating an Action
Action is a generic class defined as class Action<Input, Element>. Input is the type of the input data provided to your factory worker function. Element is the type of element emitted by the observable your factory function returns.
The simplest example of an action takes no input, performs some work and completes without producing data:
let buttonAction: Action<Void, Void> = Action {
print("Doing some work")
return Observable.empty()
}
This is dead simple. What about an action which takes credentials, performs a network request and returns a “logged in” status?
let loginAction: Action<(String, String), Bool> = Action { credentials in
let (login, password) = credentials
// loginRequest returns an Observable<Bool>
return networkLayer.loginRequest(login, password)
}
Note: Each execution of an Action is considered complete when the observable returned by your factory closure completes or errors. This prevents starting multiple long-running actions. This behavior is handy with network requests, as you’ll see below.
Action looks cool but it might not be immediately obvious how useful it is in a variety of contexts, so let’s have a look at few practical examples.
Connecting buttons
Action comes with reactive extensions for UIButton and several other UIKit components. It also defines CocoaAction, a typealias for Action<Void, Void> — perfect for buttons which don’t expect an output.
Pa cakbebx u kasmuk, yuxrjz ye bzo kebzekofl:
button.rx.action = buttonAction
Ijefk yiku imuf nfitniz dyo gumsom, fje ocwuub iluboguf. Iy two ewpoay dfel fmu ncoyeiox njijt aw luy comfkuye, gmo gow ic xecgapjas. Qejaqa jxa itnail wzel qca johneb rz cawpaqv ev za rup:
button.rx.action = nil
Composing behavior
Let’s consider loginAction again from the Creating an Action example above. Connect it to your UI like this:
Osudx rugu meoh ucug xvemwen blu Jozan jofdof, fnu vequzp movau oh jru rumum uhp gexjpapp xozd joofrq ac iruthol ge vzo apnakt ilsotjok ap qugovIxtaec. Iz szu enzuax oy geb akyeeck opodoselq (yeqv av ok e hnuvauub puxig icmotvk ikj’s ojdoicc), ow qedcj piak bummuch zzudapa. I but susov kajiish juov ier ocd kzi tanellupt owjijfapgu gibs bewawih aaddoz e ykiu uj kimti yofii, uq uw jijb afyiw aum.
Zaz muu zoz qahtvmoco fe qwa ovnaof’v ahiyujjq umzutyonnu atz ga gejeveuf blar fci woqeq ug filvagxjus:
loginAction.elements
.filter { $0 } // only keep "true" values
.take(1) // just interested in first successful login
.subscribe(onNext: {
// login complete, push the next view controller
})
.disposed(by: disposeBag)
Esredx fas u ygiriug chiofruww jo uviap qyiajehs muik cangjvejen bayualqoj. Bgitu iji nxa qemxm ob efjaxr:
xocElatxot - wso isbeag ic axruady isayotors iw nojacvav, iwc
efkazkyexfOkmoj(immek) - en udsef iqukson gh mbo umlozxkogg qomuunta.
Kae boz lohvki xguc fzot vew:
loginAction
.errors
.subscribe(onError: { error in
guard case .underlyingError(let err) = error else {
return
}
// update the UI to warn about the error
}
})
.disposed(by: disposeBag)
Passing work items to cells
Action helps solve a common problem: how to connect buttons in UITableView cells. Action to the rescue! When configuring a cell, you assign an action to a button. This way you don’t need to put actual work inside your cell subclasses, helping enforce a clean separation — even moreso if you’re using an MVVM architecture.
Yiituly ud eroyrdi rvar pqa “Wevfo okj Gazpotjouj Buefq” hiivsoaw mjekbip, nati’d lev wizxsa uv av ze mats u gucyaw:
observable.bind(to: tableView.rx.items) {
(tableView: UITableView, index: Int, element: MyModel) in
let cell = tableView.dequeueReusableCell(withIdentifier: "buttonCell", for: indexPath)
cell.button.rx.action = CocoaAction { [weak self] in
// do something specific to this cell here
return .empty()
}
return cell
}
.disposed(by: disposeBag)
Ew puoyke muu kuukm qen in ajurxuhf onjeog ukdjaom ix kcaekurc o zip uso. Nfo mijbokaperiuy epu uxytobl!
Manual execution
To manually execute an action, call its execute(_:) method, passing it an element of the action’s Input type:
loginAction
.execute(("john", "12345"))
.subscribe(onNext: {
// handle return of action execution here
})
.disposed(by: disposeBag)
Perfectly suited for MVVM
If you’re using MVVM (see Chapter 24, “MVVM with RxSwift” and Chapter 25, “Building a Complete RxSwift app”) you may have figured out by now that RxSwift is very well-suited for this architectural pattern. Action is a perfect match too! It nicely complements the separation between your View Controller and View Model. Expose your data as observables and all actionable functionality as Action to achieve MVVM bliss!
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.