RxSwift: Bài 6: RxCocoa (Part 4) - Units
RxSwift
20
swift
71
ios
55
RxCocoa
6
Male avatar

Bùi Khánh Duy viết ngày 30/06/2018

RxSwift: Bài 6: RxCocoa (Part 4) - Units

=====
Updated ngày 30/06

Updated một chút: Vì những bất tiện và không rõ ràng về thông tin của kipalog, mình mới dọn về nhà mới ở đây, thoải mái, đẹp đẽ rõ ràng hơn kipalog. Mong các bạn có thể theo dõi tại trang web của mình để cập nhật thêm nha

Mình mới viết một loạt bài về chủ đề Clean Architecture và Service Locator. Các bạn có thể theo dõi.
Page trên facebook ở đây: ở đây

=====
1. Khái niệm chung
RxCocoa cung cấp nhiều tính năng mới để làm việc với Cocoa và UIKit 1 cách dễ dàng hơn. Ngoài bindTo, nó cũng đưa ra 1 implementation đặc biệt của observables mà được tạo ra để sử dụng với UI: Units. Units là 1 nhóm các lớp có các observables đặc biệt cho phép người dùng dễ dàng viết và code đơn giản hơn, đặc biệt là khi làm việc với UI.

2. ControlProperty and Driver là gì?
Khi bạn binding observables đến UI, rõ ràng bạn cần phải luôn subscribe trên main thread để update UI, thường xuyên cần share subscriptions để bind đến nhiều thành phần UI components, và chắc chắn không hề muốn bị error hay break UI.

Với những mong muốn đó, những tính năng thực sự của Units là:

  • Units sẽ không thể bị lỗi
  • Units được quan sát và lắng nghe trên main scheduler
  • Units share những ảnh hưởng phụ

Những thằng này xuất hiện để đảm bảo UI luôn hiển thị 1 cái gì đó và data hiển thị luôn luôn được sử dụng phù hợp sao cho UI có thể handle được nó. Có hai phần chính của framework Units như sau:

• ControlProperty and ControlEvent
• Driver

ControlProperty không mới, mình đã đề cập ở những phần trước đó rồi, dùng để bind data đến UI tương ứng mà sử dụng rx riêng biệt.

ControlEvent được sử dụng để lắng nghe những sự kiện chắc chắn, như là bấm "Return" button trên bàn phím trong khi đang edit text field. 1 control event được xem là đang available (hoạt động) nếu các component sử dụng UIControlEvents để theo dõi trạng thái hiện tại của nó.

Driver là 1 special observable với những ràng buộc tương tự đã giải thích trước đó, cho nên nó không thể bị lỗi. Tất cả các tiến trình đều được đảm bảo để có thể execute trên main thread tránh làm cho UI thay đổi trên background threads.

Nhìn chung, units chỉ là phần optional, không quá cần thiết. Nếu bạn pro observable và subject và hiểu mình làm task nào trên scheduler nào.
Nhưng nếu bạn muốn 1 việc check của compiler đẹp hơn, điều kiện cho UI hợp lí hơn thì Unit rất mạnh. Đơn cử nếu không dùng Units, rất dễ quên dòng code .observeOn(MainScheduler.instance) thành ra mình đang update UI ở background thread. Thế là chết.

3. Áp dụng Driver và ControlProperty
Bây giờ ta apply vào app, đảm bảo rằng tất cả các task được thực hiện đúng thread và không có gì bị lỗi cả và dừng subscriptions từ delivering results.

Bước đầu tiên là chuyển weather data observable thành driver, tìm biến search trong viewDidLoad(), và thay bằng dòng code này:

let search = searchCityName.rx.text
  .filter { ($0 ?? "").characters.count > 0 }
  .flatMapLatest { text in
    return ApiController.shared.currentWeather(city: text ?? "Error")
        .catchErrorJustReturn(ApiController.Weather.empty)
  }
  .asDriver(onErrorJustReturn: ApiController.Weather.empty)

Đoạn code chính ở đây chính là 1 trong những dòng ở dưới asDriver(...). Đây là method mà convert observable của bạn thành Driver. Cái parameter onErrorJustReturn chỉ ra 1 giá trị default, giá trị này được sử dụng trong trường hợp Observable bị lỗi. Nó sẽ loại bỏ khả năng Driver phát ra 1 error.

Bạn có thể để ý là AutoCompletion cũng cung cấp cho mình nhiều variants khác cũng tương tự như asDriver(onErrorJustReturn:):

• asDriver(onErrorDriveWith:) với hàm này, mình có thể xử lý error manually, và return 1 sequence mới, mà sequence này được sử dụng duy nhất khi nó bị lỗi.

• asDriver(onErrorRecover:) để ý thì thấy (onErrorRecover được dùng thay cho onErrorDrive) được sử dụng cùng với Driver đang tồn tại khác. Cái này sẽ được dùng để recover cái driver mà bị lỗi ấy.

Tiếp theo, có 1 function tương tự bind(to: ) tên là drive, bạn chỉ cần thay các dòng code có tên là bind(to:) thành drive cho các subscriptions:

search.map { "\($0.temperature)° C" }
  .drive(tempLabel.rx.text)
  .dispose(by: bag)

search.map { $0.icon }
  .drive(iconLabel.rx.text)
  .dispose(by: bag)

search.map { "\($0.humidity)%" }
  .drive(humidityLabel.rx.text)
  .dispose(by: bag)

search.map { $0.cityName }
  .drive(cityNameLabel.rx.text)
     .dispose(by: bag)

Điều này sẽ restore behavior của UI 1 cách chính xác trong khi đang sử dụng Driver. Driver works khá giống như bind(to: ), sự khác nhau ở đây là cái tên nghe hợp lí hơn so với Units

Đến lúc này, cái app tận dụng khá nhiều ưu điểm của RxCocoa, tuy nhiên vẫn còn 1 vài thứ mình có thể refactor để improve.
App sử dụng rất nhiều resources và quá nhiều API requests (do mỗi lần mình gõ 1 chữ thì app lại request 1 lần).

throttle có thể là 1 ý hay nhưng vẫn sẽ nảy sinh ra nhưng requests không cần thiết. 1 cách khác là dùng ControlProperty of UITextField và bắn ra request chỉ khi user hits Search Button trên keyboard.

Tìm dòng này:

let search = searchCityName.rx.text

Và thay bằng:

let search =
searchCityName.rx.controlEvent(.editingDidEndOnExit).asObservable()
  .map { self.searchCityName.text }

Ta vẫn càn phải make sure input valid, nên bạn cần skip emty string thông qua filter (count > 0), xong rồi tiếp tục:

.flatMap { text in
  return ApiController.shared.currentWeather(city: text ?? "Error")
}
.asDriver(onErrorJustReturn: ApiController.Weather.empty)

Bây giờ app chỉ send api request chỉ khi người dùng hits Search Button. Không còn những requests vô ích nữa, code được control tại thời điểm compile bởi Units. Ta cũng đã remove catchErrorJustReturn(:) đến observable, cái mà được trả về bởi currentWeather(city:).

alt text

Lược đồ gốc đã sử dụng một single observable mà được updated đến toàn bộ UI, thông qua cái rẽ nhánh của multiple blocks, bạn đã chuyển sang subscribe để bind và sử dụng lại những observables tương tự thông qua view controller. Cách tiếp cận này làm cho code có khả năng reusable và dễ nhìn.

Ví dụ, nếu giờ mình add thêm parameter áp suất đến UI, mình chỉ cần add property này vào model, map với JSON value rồi map property đó đến new label áp suất mới tạo. Eassssy!

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

Male avatar

Bùi Khánh Duy

29 bài viết.
14 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
Male avatar
4 3
RxSwift: Bài 1 Observable và Just, Of, From ===== Updated ngày 30/06 Updated một chút: Vì những bất tiện và không rõ ràng về thông tin của kipalo...
Bùi Khánh Duy viết 9 tháng trước
4 3
Male avatar
4 0
Autolayout và lifecycle trong IOS ===== Updated ngày 30/06 Updated một chút: Vì những bất tiện và không rõ ràng về thông tin của kipalog, mình mớ...
Bùi Khánh Duy viết 7 tháng trước
4 0
Male avatar
2 3
RxSwift: Bài 2 Subscribing to observables ===== Updated ngày 30/06 Updated một chút: Vì những bất tiện và không rõ ràng về thông tin của kipalog,...
Bùi Khánh Duy viết 9 tháng trước
2 3
Bài viết liên quan
White
11 4
(Link) (Link) (Link) Ở 2 phần tut trước, mình đã hướng dẫn khá chi tiết cách viết một ứng dụng camera có tích hợp chức năng nhận diện khuôn mặ...
HoangPH viết hơn 3 năm trước
11 4
White
2 3
Xin chào mọi người. Mình xin chia sẽ một UILabel Helper nhỏ dùng trong truờng hợp cần tính chiều cao của UILabel để xác định "Show More" button có ...
DonDinh viết hơn 3 năm trước
2 3
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


Male avatar
{{userFollowed ? 'Following' : 'Follow'}}
29 bài viết.
14 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!