RxSwift là gì?
RxSwift là 1 phần của ReactiveX (thường gọi là “Rx”) được sử dụng ở rất nhiều ngôn ngữ và platform khác nhau.
ReactiveX bắt nguồn từ .Net/C#, sau đó nó phát triển mạnh mẽ với Ruby-ists, JavaScripters, đặc biệt là Java và Android
RxSwift là framework sử dụng cho ngôn ngữ Swift theo kỹ thuật reactive.
ReactiveX frameworks cung cấp các thuật ngữ chung cho tác vụ cụ thể trên tất cả các ngôn ngữ khác nhau. Điều này tiện lợi khi sử dụng trên các ngôn ngữ khác nhau, chúng ta sẽ không phải mapping các ngôn ngữ khác nhau khi thay đổi ngôn ngữ mới.
Tại sao nên sử dụng Rx
- Rx cho phép xây dựng app theo Declarative Programing*
- Composable : chương trình là sự kết hợp của nhiều component(thành phần), các thành phần này độc lập và có khả năng thay thế mà không ảnh hướng tới các thành phần khác.
- Reusable : code có khả năng tái sử dụng cao
Dễ hiểu và chính xác vì Rx nâng cao mức trừu tượng (abstraction) và loại bỏ các trạng thái trung gian. - Rx đã được thực hiện unit test nên rất ổn định
- Rx mô hình hóa ứng dụng như là unidirectional data flows (các luồng dữ liệu một hướng) do vậy sẽ giảm stateful*
- Không có memory leak <- Quản lý tài nguyên dễ dàng
Một số khái niệm cơ bản.
Observables and Observers
- Observable: something which emits notifications of change, tức là một cái mà có thể phát ra thông báo khi có sự thay đổi
- Observer: something which subscribes to an Observable, in order to be notified when it has changed. Tức là là cái mà luôn lắng nghe tới các thay đổi của Observable, khi Observable có thay đổi thì nó sẽ nhận được thông báo.
Có thể có nhiều Observers lắng nghe tới sự thay đổi của Observable, khi Observable này thay đổi thì tất cả các Obsevers đều nhận được thông báo.
The DisposeBag
RxSwift sử dụng DisposeBag kết hợp với ARC để quản lý bộ nhớ. DisposeBag này sẽ được giải phóng khi đối tượng cha của nó giải phóng.
Khi hàm deinit() được gọi ở object có chứa thuộc tính DisposeBag thì cái “túi” này sẽ giải phóng, tất cả các disposable Observer khi này sẽ tự động huỷ bỏ lắng nghe tới Observable mà nó lắng nghe lúc trước. Điều này sẽ cho phép ARC có thể giải phóng được bộ nhớ.
Nếu không sử dụng DisposeBag sẽ có 2 trường hợp có thể xảy ra: 1. Observer sẽ tạo ra retain cycle khiến cho không thể giải phóng được bộ nhớ, hoặc 2. nó sẽ được giải phóng trước khi thực hiện tác vụ nào đó dẫn tới crash app.
Demo
Để hiểu rõ hơn chúng ta sẽ cùng nhau làm 1 ứng dụng demo đơn giản City Searcher. Chức năng ứng dụng này là khi ta gõ tìm kiếm thành phố trên search bar ứng dụng sẽ cho phép lọc và hiển thị ra các thành phố có tên bắt đầu bằng các chữ đã gõ.
Thông thường khi xây dựng chức năng search trong app chúng ta sẽ phải nghĩ tới các vấn đề như: nếu như ta gõ quá nhanh khi đó sẽ có rất nhiều các API request được gọi ta sẽ phải xử lý huỷ bỏ các request trước để đảm bảo chỉ gọi request mới nhất, hay phải chờ trước khi thực hiện 1 request khác, hay như việc kiểm tra phrase xem nó có giống với phrase trước hay không để giảm thiểu việc request… điều này làm cho logic sẽ phức tạp gây khó khăn cho việc code cũng như maintain sau này. Tuy nhiên nhờ sự giúp đỡ của RxSwift các vấn đề phức tạp này đã được giải quyết.
Create project
Chúng ta sẽ tạo project mới tên là CitySearcher
Sử dụng cocoapods để install RxSwift. Nội dung Podfile như sau:
target 'CitySearcher' do
use_frameworks!
pod 'RxSwift', '~> 2.0'
pod 'RxCocoa', '~> 2.0'
end
Chạy pod install
Nếu bạn chưa biết cách sử dụng cocoapods thì xem hướng dẫn tại đây
Tạo giao diện đơn giản gồm: UISearchBar + UITableView như hình:
Để đơn giản cho demo RxSwift chúng ta sẽ không sử dụng API, thay vào đó sẽ sử dụng 2 mảng: 1 mảng sẽ lưu tên các thành phố, 1 mảng lưu các thành phố được tìm kiếm để hiển thị lên:
var shownCities = [String]() // Data source for UITableView
let allCities = ["New York", "London","London1","London2","London3", "Oslo", "Warsaw", "Berlin", "Praga"] // Our mocked API data source
Tạo các IBOutlet cho UITableView, UISearchBar, tạo UITableViewDatasource cho UITableView kết nối tới shownCities.
Thiết lập cell identifier “CityCell” cho prototype cell
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
var shownCities = [String]() // Data source for UITableView
let allCities = ["New York", "London","London1","London2","London3", "Oslo", "Warsaw", "Berlin", "Praga"] // Our mocked API data source
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shownCities.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CityCell", forIndexPath: indexPath)
cell.textLabel?.text = shownCities[indexPath.row]
return cell
}
Đoạn code trên được thực hiện như 1 tableview thông thường, nếu chung ta thay đổi giá trị của shownCities thì chúng sẽ được thể hiện trên tableview.
Bây giờ chúng ta sẽ observe text của UISearchBar. RxCocoa (extension của RxSwift) đã có sẵn hàm build in cho Cocoa Framework hỗ trợ cho việc này, chúng ta sẽ sử dụng rx_text, khi đó ta sẽ nhận được notify mỗi khi text của search bar thay đổi.
Trước tiên ta import RxSwift và RxCocoa
import RxCocoa
import RxSwift
Tạo DisposeBag
let disposeBag = DisposeBag()
Observing part: trong hàm viewDidLoad() ta sẽ observing cho rx_text của UISearchBar
searchBar.rx_text // Observable property thanks to RxCocoa
.subscribe(onNext: { [unowned self] query in
self.shownCities = self.allCities.filter { $0.hasPrefix(query) } // We now do our "API Request" to find cities.
self.tableView.reloadData() // And reload table view data.
})
.addDisposableTo(disposeBag) // Don't forget to add this to disposeBag. We want to dispose it on deinit.
subscribe sẽ được gọi mỗi khi có thay đổi text trong UISearchBar
Ngoài ra ta còn có onError, onCompleted event
DisposeBag được sử dụng để giải phóng bộ nhớ.
Build and run: Chạy thử ứng dụng, gõ 1 vài chữ tìm kiếm thành phố như “O”, “N”, “H”, … ta sẽ thấy tên các thành phố tìm kiếm được được hiển thị lên tableview như mong muốn.
Tuy nhiên các vấn đề chúng ta lo sợ ban đầu như: API spamming, Empty phrase, Delay thì sao?
Chúng ta sẽ cho delay X giây sau khi gõ text xong mới gọi API request. Trong trường hợp ví dụ ta gõ “O”, sau X giây API tìm kiếm “O” sẽ được gọi, sau đó ta gõ “OC” sau đó xoá nhanh chữ “C” ngay trước X giây, khi đó API với text “OC” chưa được gọi, thay vì đó API với text “C” lại được gọi lại lần nữa. Trong trường hợp này chúng ta sẽ có 2 API giống hệt nhau được request lên server.
RxSwift cung cấp 2 hàm sau: throttle() cho phép delay việc thực hiện sau 1 khoảng thời gian cho trước,distinctUntilChanged() sẽ ngăn chặn việc gọi các API giống nhau nhiều lần.
searchBar.rx_text // Observable property thanks to RxCocoa
.debounce(0.5, scheduler: MainScheduler.instance) // Wait 0.5 for changes.
.distinctUntilChanged() // If they didn't occur, check if the new value is the same as old.
.filter { !$0.isEmpty } // If the new value is really new, filter for non-empty query.
.subscribe(onNext: { [unowned self] query in // Here we subscribe to every new value, that is not empty (thanks to filter above).
self.shownCities = self.allCities.filter { $0.hasPrefix(query) } // We now do our "API Request" to find cities.
self.tableView.reloadData() // And reload table view data.
})
.addDisposableTo(disposeBag) // Don't forget to add this to disposeBag. We want to dispose it on deinit.
Okay, vậy là chúng ta đã có ứng dụng RxSwift đơn giản, có cái nhìn cơ bản về RxSwift.
Link source tại đây
Một số link tham khảo:
https://dangthaison91.wordpress.com/2015/10/24/declarative-programming/
https://dangthaison91.wordpress.com/2016/05/31/introduction-functional-reactive-programming/
https://chucuoi.net/2016/10/08/gioi-thieu-reactivex-swift/
https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa
http://www.thedroidsonroids.com/blog/ios/rxswift-by-examples-1-the-basics/
Leave a Comment