Getting Started With RxSwift and RxCocoa

Getting started with rxswift and rxcocoa

What is rxswift and rxcocoa

RxSwift và RxCocoa 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, trong khi RxCocoa là framework sử dụng cho các Cocoa APIs trong các ứng dụng iOS, OSX 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.

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 và RxCocoa 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 Observalbe 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.

Getting Started

Để 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.

Let’s create project

Chúng ta sẽ tạo project mới tên là CitySearcher

Sử dụng cocoapods để install RxSwift, RxCocoa. Nội dung Podfile như sau:


use_frameworks!
target "CitySearcher" do
pod 'RxSwift'
pod 'RxCocoa'
pod 'Alamofire'
end
post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['SWIFT_VERSION'] = '3.0'
        end
    end
end

Chạy pod install

pod install

Tạo giao diện đơn giản gồm: UISearchBar + UITableView như hình:

Screen Shot 2016-12-16 at 10.39.48 AM.png

Để đơ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", "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 “cityPrototypeCell” cho prototype cell

@IBOutlet weak var tableView: UITableView!
@@IBOutlet weak var searchBar: UISearchBar!

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("cityPrototypeCell", 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

var disposeBag = DisposeBag()

Observing part: trong hàm viewDidLoad() ta sẽ observing cho rx.text của UISearchBar

self.searchBar.rx.text
.subscribe(onNext: { element in
    self.shownCities = self.allCities.filter { $0.hasPrefix(element!) } // We now do our "API Request" to find cities.
    self.tableView.reloadData()
})
.addDisposableTo(disposeBag)

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.

self.searchBar.rx.text
.throttle(0.5, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.subscribe(onNext: { element in
     self.shownCities = self.allCities.filter { $0.hasPrefix(element!) }
     self.tableView.reloadData()
})
.addDisposableTo(disposeBag)

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. Ở các bài viết tiếp theo chúng ta sẽ tiếp tục cùng nhau tìm hiểu sâu hơn về RxSwift. Hẹn gặp các bạn ở các bài viết tiếp theo.

Tham khảo:

https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa

http://www.thedroidsonroids.com/blog/ios/rxswift-by-examples-1-the-basics/