Tìm hiểu RxSwift bài 1 - vì sao nên sử dụng Rx
Bài đăng này đã không được cập nhật trong 7 năm
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.
All rights reserved