قالب وردپرس درنا توس
Home / IOS Development / Getting started with RxSwift and RxCocoa

Getting started with RxSwift and RxCocoa



Update Note : Ron Kliffer updated this guide for Xcode 10, Swift 4.2, iOS 12 and RxSwift 4.4.0. Ellen Shapiro wrote the original.

It's great when code does exactly what you want (unlike my cat). Change the program, enter the code to update, and so do. Good code!

Most programming in object-oriented era has been imperative . Code tells the program what to do and has many ways to listen to changes. However, you must tell the system when something changes.

Wouldn't it be better if you could set things up so that the code updates automatically reflect changes? It is the idea of ​​ reactive programming: Your application can respond to changes in underlying data without telling you to do so. This makes it easier to focus on the logic at hand than to maintain a particular state.

You can accomplish this in Swift through the Key-Value Observation and use didSet but it can be cumbersome to set up. Alternatively, there are several frames in Swift that facilitate reactive programming.

Note : For more background, see Colin Eberhardt's great article describing the differences between large frames. Take a look at the comment section for further discussion.

In this training, you use RxSwift the framework and its companion RxCocoa to take a chocolate purchase app from imperative to reactive.

What are RxSwift and RxCocoa?

RxSwift and RxCocoa is part of the package of ReactiveX (Rx) language tools that span multiple programming languages ​​and platforms.

While ReactiveX started as part of the .NET / C # ecosystem, it grew extremely popular among Rubyists, JavaScripters and especially Java and Android developers.

RxSwift is a framework for interacting with Swift programming languages, while RxCocoa is a framework that makes cocoa APIs used in iOS and OS X easier to use with reactive techniques.

ReactiveX frames provide a common vocabulary for tasks used repeatedly across different programming languages. This makes it easy to focus on the syntax of the language itself, rather than figuring out how to chart a common task for each new language.

Observables and Observers

Two key terms are Observable and Observer .

  • A Observable sends out message changes.
  • An observer subscribes to an observable and is notified when the observable has changed.

You can have several observers listening to an observable one. When the observable changes, it will alert all its observers.

The DisposeBag

The DisposeBag is an additional tool that RxSwift provides to handle ARC and memory management. When a parent object is deleted, Observer objects depend on DisposeBag.

When deinit () is called upon the object containing DisposeBag, each disposable observer is automatically canceled from what it observed. This allows the ARC to retrieve the memory as it normally would.

Without a DisposeBag, you will get one of two results. Either the observer would create a stock cycle that depends on what it observes indefinitely, or it may be distributed and cause a crash.

To be a good ARC citizen, remember to add observable objects to DisposeBag when you set them up. DisposeBag will clean up nicely for you.

Getting Started

It's time to get to the chocolate! Use the Download Materials button at the top or bottom of this tutorial to download the startup project. Once you've done that, open Chocotastic.xcworkspace .

Build and run the application. You will see the following screen showing several types of chocolate you can buy from Europe, along with their respective prices:

 Initial State

Tap a chocolate bar to add that product to your cart: [19659002]  added chocolate

Tap the cart in the upper right corner. On the next page you can check out or reset your cart:

 checkout

Select Checkout results in a credit card information form:

 Credit Card Form

] Later in the training, you come back to set this up using purely reactive programming. For now, press the shopping basket button to return to the curve summary. Then press Reset the button to return to the main screen with an empty cart.

Starting point: Non-reactivity

Now that you've seen what the program does, it's time to investigate how it works. Open ChocolateOfTheWorldViewController.swift . You see some standard UITableViewDelegate and UITableViewDataSource extensions.

Look at updateCartButton () . This method updates the wagon button with the current number of chocolates in the shopping cart. The method updates the shopping cart in two cases:

  1. viewWillAppear (_ 🙂 : Before viewing the display controller.
  2. tableView (_: didSelectRowAt 🙂 : After a user adds a new chocolate to the cart.

These are both imperative ways to change the count. You must explicitly call the method to update the count.

You should rewrite the code to use a reactive technique. That way, the button will be updated on its own.

RxSwift: Make Shopping Cart Reactive

The methods that refer to items in the shopping cart use a ShoppingCart.sharedCart singleton. Open ShoppingCart.swift and you will see a default layout of a variable in the singleton example:

  was chocolate: [Chocolate] = []

At this time, you cannot observe changes in content of chocolate . You can add a didSet shutdown to its definition, but it will only be called when the entire update is updated, instead of some of its items.

Fortunately, RxSwift has a solution. Replace the line that makes the chocolate variable with this:

  la chocolate: BehaviorRelay <[Chocolate]> = BehaviorRelay (value: [])

Note : This change will cause errors in the sidebar. You will fix them in an instant.

This syntax can be a bit tricky to wrap your head around. It helps to understand what is happening. Basically, instead of putting chocolate to a weak selection of Chocolate objects, you have now defined it as an RxSwift BehaviorRelay which has a type of a weak selection of chocolate objects.

Behavior Relay is a class so it uses reference semantics. This means that chocolate refers to an instance of BehaviorRelay .

BehaviorRelay has a property called value . This stores your reach of Chocolate objects.

The magic of BehaviorRelay comes from a method called asObservable () . Instead of manually checking the value each time, you can add a Observer to keep an eye on the value for you. When the value changes, the observer lets you know so you can respond to any updates.

The disadvantage is that if you need to access or change something in that amount of chocolate, you must do so via accept (_ :) . This method on BehaviorRelay updates its value property. That's why the compiler throws a tantrum and presents a fistful of bugs. Time to fix them!

In ShoppingCart.swift you find the method totalCost () and change this line:

  return chocolates.reduce (0) {

To:

  return chocolates.value.reduce (0) {

itemCountString () change:

  guard chocolates.count> 0 else {

To:

  guard chocolates.value.count> 0 else {

And change:

  la setOfChocolates = Set  (chocolate)

To:

  la setOfChocolates = Set  (chocolates.value)

Finally, change:

  la count: Int = chocolates.reduce (0) {

To:

  la count: Int = chocolates.value.reduce (0) {

CartViewController.swift find reset () and change:

  ShoppingCart.sharedCart.chocolates = []

To:

  ShoppingCart.sharedCart.chocolates. accept (19459053)

Back in ChocolateOfTheWorldViewController.swift change the implementation of updateCartButton () to this:

  cartButton.title = "(ShoppingCart.sharedCart.chocolates.value.count) u {1f36b} "

And in tableView (_: didSelectRowAt :) change this line:

  ShoppingCart.sharedCart.chocolates.append (chocolate)

To the following:

  la newValue = ShoppingCart.sharedCart.chocolates.value + [chocolate]
ShoppingCart.sharedCart.chocolates.accept (newValue)

Whew! It would make Xcode happy and take care of the mistakes. Now you can take advantage of reactive programming and observe chocolate !

Go to ChocolatesOfTheWorldViewController.swift and add the following to the properties list:

  private la disposeBag = DisposeBag ()

This creates DisposeBag you will use to clean up any observers you have set up.

Add the following to the extension under // MARK: Rx Setup comment:

  func setupCartObserver () {
// 1
ShoppingCart.sharedCart.chocolates.asObservable ()
.subscribe (onNext: {// 2
[unowned self] chocolate in
self.cartButton.title = "(chocolates.count) u {1f36b}"
})
.disposed (by: disponerebag) // 3
}

This sets up a reactive Observer to automatically update your shopping cart. As you can see, RxSwift makes great use of chained features, which means each function takes the result of the previous function.

How it happens in this case:

  1. Take the shopping cart chocolate variable as a observable . Subscribe (Next) on the Observable to discover changes in the value of Observable . subscribe (onNext :) accepts a close which is performed each time the value changes. The incoming parameter of the shutdown is the new value of Observe . You will continue to receive these alerts until you either unsubscribe or dispose of your subscription. What you get back from this method is a Observer according to Available .
  2. Add Observe from the previous step to Disposal Bag ]. This depends on your subscription by deallocation of the subscription.

To complete, delete the important updateCartButton () . This will cause errors to appear when called in viewWillAppear (_ :) and tableView (_: didSelectRowAt :) .

To fix them, delete the entire viewWillAppear (_ :) page call updateCartButton () is the only thing they do except to call super . Then the call is deleted to updateCartButton () in tableView (_: didSelectRowAt :) .

Build and run. You will see the list of chocolates:

 RXCocoa updated list of chocolates

Uh-oh. The wagon button only says Goods, and when you start to tap the chocolate list, nothing happens. What went wrong?

You created a function to set up your Rx observers, but there is nothing that calls that feature, so the observers do not respond. To fix this, open ChocolatesOfTheWorldViewController.swift and add the following at the end of viewDidLoad () :

  setupCartObserver ()

Build and run the program to see the list of chocolates again:

 startstat

Click on some chocolates – the number of items in your shopping cart is now updated automatically!

 added chocolate

Success! You can now add all the chocolates to your shopping cart.

RxCocoa: Make TableView Reactive

Now that you've made the carriage reactive using RxSwift, use RxCocoa to make UITableView reactive as well.

RxCocoa has reactive APIs for several different types of UI elements. These allow you to create table views without mandatory delegate or data source methods.

To demonstrate this, delete all UITableViewDataSource and UITableViewDelegate extensions from ChocolatesOfTheWorldViewController.swift . Then, the tasks of tableView.dataSource and tableView.delegate are deleted from viewDidLoad () .

Build and run the program and you will see that your happy little table full of chocolate has become sad and empty:

 no chocolate for you

It's not fun. Time to restore chocolate!

A reactive table view needs something for the table view to respond to. In ChocolatesOfTheWorldViewController.swift you update the europeanChocolates property to be a Observable :

  la EuropeanChocolates = Observable.just (Chocolate.ofEurope)
 only (_ :)  indicate that there will be no changes in the underlying value of  Observe  but that you will still access it as a  Obvious  value. 

Note : Sometimes calls only (_ :) an indication that the use of reactive programming may be overkill. After all, if a value never changes, why use a programming technique designed to respond to change? In this example, you use it to set up reactions of table display cells that will change. However, it is a good idea to look carefully at how to use Rx. Just because you have a hammer does not mean that every problem is a nail. :]

Now add the following function to the extension labeled as // MARK: - Rx Setup :

  func setupCellConfiguration () {
// 1
europeanChocolates
. bind (to: tableView
.rx // 2
.items (cellIdentifier: ChocolateCell.Identifier,
cellType: ChocolateCell.self)) {// 3
row, chocolate, cell in
cell.configureWithChocolate (chocolate: chocolate) // 4
}
.disposed (by: disponerebag) // 5
}

What happens here:

  1. Call bind (to :) to associate EuropeanChocolates observable with the code that runs each row in the table view.
  2. By calling rx you can access the RxCocoa extensions for that class. In this case, it is a UITableView . Call the Rx method elements (cellIdentifier: cellType :) pass in the cell identifier and the class of the cell type you want to use. The Rx frame calls dequeuing methods as if your table view had the original data source.
  3. Pass in one block for each new item. Information about the row, chocolate on that row and the cell returns. Disposable returned by bind (to :) and add it to disposeBag .

The values ​​generated by tableView (_: numberOfRowsInSection :) and numberOfSections (i :) are now automatically calculated based on the observed data. The closure effectively replaces tableView (_: cellForRowAt :) .

Go to viewDidLoad () and add a line calling the new setup method:

  setupCellConfiguration ()

Build and run the program, and voilà! Your chocolate is back!

 starting state

When you try to hit each chocolate, they are not displayed in the shopping cart. Do you break something with your previous Rx method?

Nope! Removes tableView (_: didSelectRowAt :) took away all that recognized cell cranes or knows how to handle them.

To correct this, there is another expansion method RxCocoa adds UITableView called modelSelected (_ :) . This returns a Observe you can use to view information about selected model objects.

ChocolatesOfTheWorldViewController.swift adds the following method in // MARK: - Rx Setup extension:

  func setupCellTap Action () {
Table View
.rx
.modelSelected (Chocolate.self) // 1
.subscribe (onNext: {[unowned self] chocolate in // 2
la newValue = ShoppingCart.sharedCart.chocolates.value + [chocolate]
ShoppingCart.sharedCart.chocolates.accept (newValue) // 3

if la selectedRowIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow (by: selectedRowIndexPath, animated: true)
} // 4
})
.disposed (by: disponerebag) // 5
}

Here is what does, step by step:

  1. Call the table view's reactive expansion model model selected (_ :) and pass in the Chocolate model type to get the right type of object back . This returns a Observable . Consider Observable call subscribe (inNext :) and enter a closure of what to do when a model is selected (ie, a cell is drained).
  2. Enter your chosen chocolate in the shopping cart in
  3. .
  4. Also in the closing you clear the dropped row.
  5. subscribe (next time :) returns a Available . Add it to disposeBag .

Finally go to viewDidLoad () and add a line calling to the new setup method:

  setupCellTapShopping ()

Build and run. You will see your confidential list of chocolates, but now you can add them to your heart's content!

 added chocolate

RxSwift and Direct Text Input

RxSwift can both take and respond to direct text input by the user.

To get a taste of handling the text recording responsively, try adding validation and card type detection to the credit card entry form.

A tangle of UITextFieldDelegate methods handles credit card entry in non-reactive programs. Often each contains a root of if / else statements that indicate the actions and logic to be used based on the text field you are editing.

Reactive programming binds the management more directly to each input field and clarifies what the logic applies to which text field.

Go to BillingInfoViewController.swift . Add the following after the other declared properties:

  privately la disposeBag = DisposeBag ()

As previously, this defines a DisposeBag to dispose of all of your observables.

It is nice for users to see what type of credit card they deposit on the basis of known card types. [19659002] To do this, add the following to the extension under // MARK: - Rx Setup comment:

  func setupCardImageDisplay () {
Short type
.asObservable ()
.subscribe (onNext: {[unowned self] cardType in
self.creditCardImageView.image = cardType.image
})
.disposed (by: dispose of bag)
}

What happens here:

  1. Add a Observer to the value of a BehaviorRelay .
  2. Subscribe to it Observable to reveal changes in cardType .
  3. Make sure the observer's disposal in disposal bag .

You use this to update the map image based on changes in the card type. Now for the fun part: Text Change Management.

Since a user can write quickly, run validation for each keystroke can be computationally expensive and lead to a flexible user interface. Instead, debounce or throttle how quickly the user's input moves through a validation process. This means that you only validate the input at the gas range instead of every time it changes. In this way, fast tasting will not grind the entire app to a stop.

Throttling is a specialty for RxSwift since it is often a good logic that should be run when something changes. In this case, a small throttle valve is worth it.

First, add the following only under the other property statements in BillingInfoViewController :

  privately let the gas column Interval = 0.1

This defines a constant for the gas length in seconds. Now add the following to the RX Setup extension:

  func setupTextChangeShopping () {
la creditCardValid = creditCardNumberTextField
.rx
.text // 1
.distinctUntilChanged ()
.throttle (throttleInterval, planner: MainScheduler.instance) // 2
. map {[unowned self] i
self.validate (cardText: $ 0) // 3
}

creditCardValid
.subscribe (onNext: {[unowned self] i
self.creditCardNumberTextField.valid = $ 0 // 4
})
.disposed (by: disponerebag) // 5
}

What this code does:

  1. Return the contents of the text field as an observable value. text is another RxCocoa extension, this time to UITextField .
  2. Repeat the entry to configure validation to run based on the interval defined above. scheduler parameter is a more advanced concept, but the short version is that it is bound to a thread. To keep everything in the lead, use MainScheduler .
  3. Transform gas inlet using it to validate (cardText :) provided by the class. If the card entry is valid, the final value of the observed Boolean will be true .
  4. Take the value Observable you created and subscribed to, updates the validity of the text field based on incoming value.
  5. Add the resulting Disposable to disposeBag .

The expiry date code and CVV validation follow the sample pattern.
Add the following code to the bottom of setupTextChangeShopping () :

  la expirationValid = expirationDateTextField
.rx
.text
.distinctUntilChanged ()
.trottle (throttleInterval, planner: MainScheduler.instance)
. map {[unowned self] i
self.validate (expirationDateText: $ 0)
}

expirationValid
.subscribe (onNext: {[unowned self] i
self.expirationDateTextField.valid = $ 0
})
.disposed (by: dispose of bag)

la cvvValid = cvvTextField
.rx
.text
.distinctUntilChanged ()
. map {[unowned self] i
self.validate (cvvText: $ 0)
}

cvvValid
.subscribe (onNext: {[unowned self] i
self.cvvTextField.valid = $ 0
})
.disposed (by: dispose of bag)

Now that you have Observable values ​​set up for the validity of the three text fields, add the following at the bottom of setupTextChange Action () :

  let altValid = Observable
.combineLatest (creditCardValid, expirationValid, cvvValid) {
$ 0 && $ 1 && $ 2 // All must be true
}

everythingValid
.bind (to: buyButton.rx.isEnabled)
.disposed (by: dispose of bag)

This uses the Observable's combineLatest (_ :) to capture the three observations you've already made and generate a fourth. The generated Observable called everythingValid is either true or false depending on whether all three inputs are valid.

altValid reflects the isEnabled property on UIButton 's reactive extension. altValid & # 39; s value controls the condition of the purchase button.

If all three fields are valid, the underlying value of everythingValid will be true . If not, the underlying value will be false . In either case, [x459007] rx.isEnabled will use the value of the purchase button, which is only enabled when all credit card information is valid.

Now that you have set up setup methods, add the code to call them viewDidLoad () :

  setupCardImageDisplay ()
setupTextChangeShopping ()

Build and run the application. To get to the credit card entry, tap a chocolate to add it to your shopping cart, and then tap the shopping cart to go to the shopping cart. As long as you have at least one chocolate in your cart, the checkbox must work:

 checkout

Press ] Check the button, which takes you to the credit card entry screen:

 Credit Card Form

Type 4 into Short Dial ]

  Show

Delete 4, and the map image returns to unknown. Enter 55 and the image will change to MasterCard.

 Simulator screen Shot July 4, 2016, 7.47.36 PM

Neat! This app covers the four motherboards in the United States: Visa, MasterCard, American Express and Discover. If you have one of these types of credit cards, you can enter the number to see the correct picture showing up and see if the number is valid.

Note : If you do not have one of these credit cards, use one of the test card numbers that PayPal uses to test their card sandbox. These should pass all local validations in the application, even if the numbers themselves are not usable.

When a valid credit card number is entered with an expiration date and CVV, will buy chocolate! button enable:

 enabled_checkout

Press the button to see a summary of what you bought and how you paid for it, as well as a small Easter egg:

 success

Congratulations! Thanks to RxSwift and RxCocoa, you can buy as much chocolate as your dentist will let you get away with.

Where to go from here?

Download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

If you want a challenge, try adding a few things to make this program even more reactive:

  • Change CartViewController to use a reactive table view (instead of a label) to view the contents of the shopping cart.
  • Allow user to add or remove chocolates directly from the cart, automatically updating the price.

Now that you've got a taste of Rx programming, here are a few resources to help you continue learning:

Finally, our own Marin Todorov has a great blog about his experiences in Reactive Programming called rx_marin. Check it out!

If you liked this tutorial, check out our RxSwift book, available in our store.

Here's a taste of what's in the book:

  • Getting Started : Get an introduction to the reactive programming paradigm, learn the terminology involved, and see how to start using RxSwift in your projects.
  • Event Management : Learn how to handle asynchronous event sequences via two key terms in Rx - Observables and Observers.
  • Being selective : See how to work with different events using terms such as filtering, transforming, combining and time operators.
  • UI Development : RxSwift makes it easy to work with the user interface of your apps using RxCocoa, which integrates both UIKit and Cocoa.
  • Intermediate Topics : Level up your RxSwift knowledge with chapters on reactive network, multi-threading and error management.
  • Advanced Topics : Round out your RxSwift education by learning about MVVM app architecture, scene-based navigation, and postponing data through services.
  • Much, much more!

At the end of this book, you'll have hands-on experience solving common issues in a reactive paradigm and be well on your way to coming up with your own Rx patterns and solutions!

Have questions or other Rx resources to suggest? Sound off below in the comments, or in the forums.


Source link