Tìm hiểu RxSwift bài 1 - vì sao nên sử dụng Rx

Mở đầu

Rx là một khái niệm trừu tượng chung của việc tính toán thể hiện qua interface Observable<Element>. Rxswift là một library của Rx viết bằng swift.

Vì sao lại dùng Rx

Rx cho phép build app theo cách khai báo.

Bindings

Observable.combineLatest(firstName.rx.text, lastName.rx.text) { $0 + " " + $1 }
    .map { "Greetings, \($0)" }
    .bind(to: greetingLabel.rx.text)

Chúng ta cũng có thể viết Tableview theo cách này

viewModel
    .rows
    .bind(to: resultsTableView.rx.items(cellIdentifier: "WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
        cell.title = viewModel.title
        cell.url = viewModel.url
    }
    .disposed(by: disposeBag)

Retries

Chúng ta thường làm việc với API, nếu API fail vì một lý do nào đó mà muốn retry lại API call trước trả ra mã lỗi. Có API

func getUserInformation(userID: String) throws -> UserInfo

Với cách truyền thống khá là khó để viết hàm retry cho hàm này nhưng với Rx bạn có thể làm nó đơn giản như sau.

getUserInfomation(userID: "userID").retry(3)

Bạn cũng có thể dễ dàng viết 1 hàm custom retry operation cho phù hợp với dự án.

Delegate

Thay vì viết delegate theo cách truyền thống

public func scrollViewDidScroll(scrollView: UIScrollView) { [weak self] // what scroll view is this bound to?
    self?.leftPositionConstraint.constant = scrollView.contentOffset.x
}

Ta có thể dùng rx để viết lại như sau:

self.resultsTableView
    .rx.contentOffset
    .map { $0.x }
    .bind(to: self.leftPositionConstraint.rx.constant)

KVO

Với các truyền thống việc khai báo, làm việc với KVO khá là rườm rà. Các value change đều trả vào cùng một hàm và khả năng leak memory cao nếu developer quên un-observer.

-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary *)change
                      context:(void *)context

Với Rx chúng ta có thể dùng rx.observe và rx.observeWeakly. Observer frame của view thay đổi rất dễ dàng.

view.rx.observe(CGRect.self, "frame")
    .subscribe(onNext: { frame in
        print("Got new frame \(frame)")
    })
    .disposed(by: disposeBag)

Notification

Có thể viết notification dưới dạng:

NotificationCenter.default
    .rx.notification(NSNotification.Name.UITextViewTextDidBeginEditing, object: myTextView)
    .map {  /*do something with data*/ }

Transient state

Có rất nhiều vấn đề với transient state khi viết ứng dụng đa luồng. Một ví dụ đơn giản là autocomplete search box. Có một vấn đề khi chúng ta viết search box theo cách truyền thống là chuỗi nhập vào mới trong khi chuỗi cũ vẫn đang process. Chúng ta cần cancel request cũ và thay bằng request với chuỗi mới. Vẫn đề thứ 2 là nếu API fail và chúng ta cần retry ở trường hợp này cũng rất khó khăn cho developer. Với Rx chúng ta có thể giải quyết được bài toán này khá ngon lành.

searchTextField.rx.text
    .throttle(0.3, scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query in
        API.getSearchResults(query)
            .retry(3)
            .startWith([]) // clears results on new search term
            .catchErrorJustReturn([])
    }
    .subscribe(onNext: { results in
      // bind to ui
    })
    .disposed(by: disposeBag)

Rx sẽ kiểm soát việc binding data và delay việc fire API cũng như retry API khi có lỗi.

Aggregating network requests

Nếu bạn có 2 request API và cần thông tin từ cả 2 cùng 1 lúc thì với cách truyền thống, bạn có thể dùng operator để viết nhưng việc khai báo và xử lý cũng tốn thời gian. Với Rx chúng ta có thể viết

let userRequest: Observable<User> = API.getUser("me")
let friendsRequest: Observable<[Friend]> = API.getFriends("me")

Observable.zip(userRequest, friendsRequest) { user, friends in
    return (user, friends)
}
.subscribe(onNext: { user, friends in
    // bind them to the user interface
})
.disposed(by: disposeBag)

Trên đây là một số cách dùng của RX thay thế cho cách code truyền thống. Chúng ta sẽ tìm hiểu sâu hơn các khái niệm khác của Rx trong bài viết sau.