About this series
This series is to help people to get started with RxSwift easily with straight forward examples, plain explanation. And YES, if you would like to spend 2 hours with a bit efforts, I am quite confident you will master the RxSwift from zero to hero.
This is the part 1 of this series which will cover most of the topic on RxSwift, from the basic concept and usage to some advanced techniques. The whole series is using RxSwift 6.5.0 (latest as for now) and Swift 5.0.
Roadmaps:
Part 1: Introduction and Quick example
Part 2: Core concepts
Part 3: Observable in depth
Part 4: Subscriber in depth
Part 5: Scheduler
Part 6: Common Operators
Part 7: RXSwift with MVVM
Introduce RxSwift
RxSwift is a popular reactive programming framework for Swift, which allows us to write code that responds to changes and events.
Before dive into the RxSwift, let's have a basic under standing on what is the Reactive programming. Reactive programming is a programming paradigm which can efficiently handle asynchronous data streams, by using a set of operators to transform and manipulate these streams, such as user's input, UI change, network responses and so on.
Why need RxSwift
Compared with the existing programing paradigm - Functional programming, there are few benefits.
- RxSwift enhances code reusability and reduces the amount of code.
- RxSwift makes it easier to understand business logic, abstract asynchronous programming, and unify code style, therefore make the code more readable and easier to maintain.
- RxSwift makes it easier to write integrated unit tests, increasing code stability.
We will use a short example on above characters.
Quick example of using RxSwift
1, Integrate RxSwift framework into project
Like many other 3rd party libraries, we need to install the framework. There are a couple of ways of doing so as fully documented on its github page: https://github.com/ReactiveX/RxSwift. Notice that the current latest version is RxSwift 6.5.0. However, there is an issue with the Swift Package Manager (SPM) installation. So we can either follow their workaround or use other means.
2, Functional programming way
Imagine we are doing a view which will verify user's input on their email address. Here is what we used to do:
//
// ViewController.swift
// RXSwiftExercise
//
// Created by Ben Liu on 6/3/2023.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var emailErrorInfo: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
emailTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
@objc func textFieldDidChange(_ textField: UITextField) {
guard let email = textField.text else {
return
}
if email.contains("!") {
emailErrorInfo.text = "Invalid character"
}
}
}
This will create a view, and once the user input a invalid character such as !
, the error message will display.
Functional programming example 1
However, there are few problems with above method:
1, When user delete the !
in the input text field, the error message is still there, which is quite confusing
2, If we have multiple text inputs such as we want to add user's username, mobile phone number and more, we need to add check on the textfield. See the code below:
//
// ViewController.swift
// RXSwiftExercise
//
// Created by Ben Liu on 6/3/2023.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var emailErrorInfo: UILabel!
@IBOutlet weak var mobileTextField: UITextField!
@IBOutlet weak var mobileErrorInfo: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
emailTextField.addTarget(self, action: #selector(emailTextFieldDidChange(_:)), for: .editingChanged)
mobileTextField.addTarget(self, action: #selector(mobileTextFieldDidChange(_:)), for: .editingChanged)
}
@objc func emailTextFieldDidChange(_ textField: UITextField) {
guard let email = textField.text else {
return
}
if email.contains("!") {
emailErrorInfo.text = "Invalid character"
} else {
emailErrorInfo.text = ""
}
}
@objc func mobileTextFieldDidChange(_ textField: UITextField) {
guard let email = textField.text else {
return
}
if email.contains("@") {
mobileErrorInfo.text = "Invalid character"
} else {
mobileErrorInfo.text = ""
}
}
}
There are still some problems that not resolved yet:
1, How to unit test the textfields rules
2, How to handle multiple textfields, i.e. 10 inputs, we have to create 10 TextFieldDidChange
methods to deal with that in the worst case.
This stage of Source code can be found on Github.
3, Refactor with RxSwift
Time to show the power of RxSwift:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var emailErrorInfo: UILabel!
@IBOutlet weak var mobileTextField: UITextField!
@IBOutlet weak var mobileErrorInfo: UILabel!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupBinding()
}
func setupBinding() {
emailTextField.rx.text.changed.subscribe(onNext: {
guard let text = $0 else { return }
self.emailErrorInfo.text = text.contains("!") ? "Invalid character" : ""
}).disposed(by: disposeBag)
mobileTextField.rx.text.changed.subscribe(onNext: {
guard let text = $0 else { return }
self.mobileErrorInfo.text = text.contains("@") ? "Invalid character" : ""
}).disposed(by: disposeBag)
}
}
With just 8 lines, we make the code much simpler and concise. More importantly we can handle as many new textfields as we want, such as user names, address and so on.
4, Test with the textfields
Here since we are using the text change as the source input, it is better to test with the UI test.
import XCTest
final class RXSwiftExerciseUITests: XCTestCase {
override func setUpWithError() throws {
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testTextFields() throws {
let app = XCUIApplication()
app.launch()
// test email
let emailTextField = app.textFields["email"]
emailTextField.tap()
emailTextField.typeText("123")
let emailErrorLabel = app.staticTexts["emailError"]
emailTextField.tap()
emailTextField.typeText("!")
XCTAssertEqual(emailErrorLabel.label, "Invalid character")
// test mobile
let mobileTextField = app.textFields["mobile"]
mobileTextField.tap()
mobileTextField.typeText("456")
let mobileErrorLabel = app.staticTexts["mobileError"]
mobileTextField.tap()
mobileTextField.typeText("@")
XCTAssertEqual(mobileErrorLabel.label, "Invalid character")
}
}
This stage of Source code can be found on Github.
Compared with Combine
Combine was introduced by Apple in 2019, which is another reactive programming framework comes with iOS 13+. Here are few comparisions:
1, Compatibility
RxSwift has been available since 2015 and supports iOS 8 and later, while Combine only supports iOS 13 and later.
2, API Design
RxSwift is designed to be more flexible and configurable, with a larger number of operators and customization options. On the other hand, Combine has a more concise and streamlined API, with a focus on Swift's built-in functional programming features.
3, Support
There are a large amount of projects already using RxSwift, so getting to know and master RxSwift is a necessary skill to get a job.
What is next
In the next few articles, we will dig more depth into the RxSwift, first start with some core concepts such as what is Observable
, Subscriber
, Subject
and Operator
.
Top comments (1)
I find it informative that this is the first part of a series that will cover a comprehensive range of topics on RxSwift. It's good to know that is using the actual version of RxSwift (6.5.X) and Swift 5.X. This ensures access to up-to-date information and techniques in learning RxSwift. I'm excited to read the upcoming articles in this series and gain a deeper understanding of rx framework.