Contents
Chào bạn đến với Fx Studio. Hành trình của chúng ta trong thế giới Rx vẫn còn rất dài. Và bài viết này sẽ tiếp tục đưa tới cho bạn một khái niệm mới trong RxCocoa. Đó là RxCocoa Traits.
Với Traits thì nó không mới, chúng ta đã có một bài viết về RxSwift Traits rồi. Nên thay vì tìm định nghĩa, chúng ta sẽ phân tích những gì có trong RxCocoa. Cuối cùng, bạn cần nắm lại một số kiến thức sau:
Và mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
-
- Xcode 12
- Swift 5.3
- RxSwift 5.0
Chúng ta vẫn dùng lại project của bài trước. Và vẫn với một màn hình là WeatherCityViewController, để hiện thị thông tin thời tiết của một thành phố nào đó. Ngoài ra, bạn có thể checkout source code demo ở đây.
-
- Link: checkout
- Thư mục:
/Examples/BasicRxSwift
Đối với bài này, ta sẽ demo code không quá nhiều. Và bạn có thể tự tạo một demo riêng cho dễ hiểu, như vậy sẽ hiệu quả hơn.
1. RxCocoa Traits
Bắt đầu, chúng ta đi sơ một số khái niệm Traits trong không gian RxCocoa trước. Về khái nhiệm thì như sau:
Trait là một wrapper struct với một thuộc tính là một Observable Sequence nằm bên trong nó. Trait có thể được coi như là một sự áp dụng của Builder Pattern cho Observable.
Về đặc trưng, RxCocoa là không gian mở rộng cho iOS. Chúng sẽ làm việc với các thành phần giao diện và trên Main Thread là chủ yếu. Do đó, các Traits này sẽ có các đặc tính tương tự.
Chúng có các nhóm sau:
- Driver
- Signal
- ControlProperty & ControlEvent
- BindingProperty
Nó cũng không quá nhiều và cũng khá là EZ. Tiếp theo, chúng ta sẽ đi vào đặc điểm & tính chất của từng loại.
2. Driver
2.1. Khái niệm
Đây là Trait được xem hoàn thành đầy đủ nhất trong RxCocoa. Mục đích của nó cung cấp một cách trực quan để viết code Rx với UI Control hoặc bất cứ khi nào mà bạn muốn đưa luồng dữ liệu từ model lên UI.
Đặc điểm của nó như sau:
- Không tạo ra lỗi.
- Observe & Subscribe trên Main Scheduler.
- Có chia sẻ Side Effect.
Nó mang tên Driver thì cũng với mục đích chuẩn bị cho bạn, khi bạn phát triển mô hình lên cao cấp hơn như MVVM, VIPER …
- Điều khiển UI từ Model
- Điều khiển UI từ sử dụng các biến từ các UI khác
- Two-way binding
Vì chỉ hoạt động trên Main Thread và không bao giờ sinh ra lỗi. Nên nó hoàn toàn tương thích và an toàn với các UI Control. Bạn sẽ không cần lo lắng gì khi sử dụng nó.
2.2. Áp dụng
Bạn mở file WeatherCityViewController và tìm tới đoạn code tạo Observable từ việc giá trị text
của UITextField.
let search = searchCityName.rx.text.orEmpty .filter { !$0.isEmpty } .flatMap { text in return WeatherAPI.shared.currentWeather(city: text).catchErrorJustReturn(Weather.empty) } .share(replay: 1) .observeOn(MainScheduler.instance)
Bạn để ý các toán tử sau:
.catchErrorJustReturn
nó giúp triệt tiêu error và thay error bằngempty
..share(replay: 1)
để tạo ra bộ đệm. Nhằm để các subscription sau khi đăng kí tới, thì không gọi lại API mà chúng sẽ sử dụng kết quả ở lần gọi trước đó..observeOn(MainScheduler.instance)
vì là tương tác với UI Control nên phải ở Main Thread, nếu không sẽ crash chương trình.
Bạn thấy chúng vất vả phải không nào. Giờ sang thử dùng Trait Driver xem như thế nào. Thay đoạn code trên bằng đoạn này.
let search = searchCityName.rx.text.orEmpty .filter { !$0.isEmpty } .flatMap { text in return WeatherAPI.shared.currentWeather(city: text).catchErrorJustReturn(Weather.empty) } .asDriver(onErrorJustReturn: Weather.empty)
Các phần trên giờ được thay bằng 1 dòng lệnh duy nhất .asDriver(onErrorJustReturn: Weather.empty)
. Nó sẽ bao quát được hết các trường hợp, kể cả API trả về error nữa.
Để đảm bảo cho quá trình chuyển đổi từ 1 Observable thành Driver, thì áp dụng toán tử asDriver(...)
. Và toán tử này có 3 phiên bản
.asDriver(onErrorJustReturn:)
trả về 1 giá trị nào đó, có thể empty nếu có error ….asDriver(onErrorDriveWith:)
phương thức dành cho các bạn trẻ đam mê bộ môn handler error. Khi xử lý xong error, thì hãy nhớ trả về 1 Driver mới nha..asDriver(onErrorRecover:)
cứu nét nó bằng một Driver khác
3.3. Cập nhật giao diện
Trên là cách biến đổi 1 Observable thành 1 Driver. Tiếp theo là cách sử dụng Driver cho UI Control.
search.map { "\($0.temperature) °C" } .drive(tempLabel.rx.text) .disposed(by: bag) search.map { $0.cityName } .drive(cityNameLabel.rx.text) .disposed(by: bag) search.map { "\($0.humidity) %" } .drive(humidityLabel.rx.text) .disposed(by: bag) search.map { $0.icon } .drive(iconLabel.rx.text) .disposed(by: bag) search.map { $0.cityName } .drive(self.rx.title) .disposed(by: bag) search.map { $0.cityName } .drive(self.rx.title) .disposed(by: bag)
Cũng không có gì khó ở đây, bạn thay bind(to:)
bằng drive(:)
là được. Thử build lại project và cảm nhận kết quả thôi.
3. Signal
Về Signal tương tự như Driver. Có một chút khác biệt là nó sẽ không share
giá trị. Vì vậy, nó không phát lại giá trị cho các subscriber mới.
Nó sẽ phù hợp với các model events, còn Driver thì là model state.
Đặc điểm như sau:
- Không có error
- Hoạt động trên Main
- Có share resource
- Không replay element
Phần này mình không có ví dụ code nha!
4. Control Property
Observable + Property = ControlProperty
Khi bạn có Observable kết hợp với property của một UI Control, thì bạn có được ControlProperty.
- ControlProperty là một phần của Observable/ObservableType. Nó đại diện cho các property của các thành phần UI.
- ControlProperty giúp chúng ta có thể thay đổi giá trị của một property trong UIComponent.
- Các ControlProperty của các thành phần giao diện hầu hết đã được cung cấp bởi RxCocoa.
Phần code ví dụ thì bạn đã làm quá nhiều rồi. Điển hình ở phần Driver thì bạn đã thay đổi giá trị text
của các UITextField bằng ControlProperty là .rx.text
Bạn tìm tới file UISwitch+Rx.swift trong phần CocoaPod RxCocoa thì sẽ thấy rõ.
extension Reactive where Base: UISwitch { /// Reactive wrapper for `isOn` property. public var isOn: ControlProperty<Bool> { return value } /// Reactive wrapper for `isOn` property. public var value: ControlProperty<Bool> { return base.rx.controlPropertyWithDefaultEvents( getter: { uiSwitch in uiSwitch.isOn }, setter: { uiSwitch, value in uiSwitch.isOn = value } ) } }
Bạn hãy tự khám phá thêm nha!
5. Control Event
5.1. Khái niệm
Ngoài các giá trị ra, thì bất cứ sự kiện nào do các UI phát sinh ra đều cũng được Rx hoá hết. Chúng là các Trait trong RxCocoa và được gọi là cái tên thân thương ControlEvent.
- ControlEvent là một phần của Observable/ObservableType. Nó đại diện cho các sự kiện của các thành phần UI.
- ControlEvent cho phép chúng ta lắng nghe những sự kiện thay đổi tới từ các UIComponent, ví dụ như UIButton được bấm, UITextField được nhập text từ người dùng,…
- Do có thể theo dõi và nhận các sự kiện của UIComponent thông qua ControlEvent nên chúng ta có thể không cần tạo các IBAction trong source code mà vẫn có thể handle được các sự kiện đến từ UI. Điều đó giúp cho source code trở nên gọn hơn và dễ dàng bảo trì.
Đặc tính như sau:
- Không thất bại
- Không error
- Hoạt động ở Main
- Không gởi giá trị ban đầu cho các subscription
- Nó sẽ kết thúc khi UI Control đó kết thúc (deinit)
Và đây là ví dụ xem là huyền thoại nè.
Button.rx.tap.asDriver() .drive(onNext: { _ in print("Button tap !") }) .disposed(by: disposeBag)
Bạn thêm nó vào code của bạn, thì thèn ngồi bên sẽ lác mắt cho xem. Thời điểm này coi như khỏi phải khéo thả các IBAction mệt mỏi nhoé.
5.2. Áp dụng
Vẫn quay về đoạn code tạo Driver search
ở phần Driver. Bạn build ứng dụng và thấy mỗi lần gõ chữ, thì chương trình chúng ta phản ứng lại ngay tức thì. Các API sẽ gọi liên tiếp nhau, mặc dù ta biết chắc là các từ khoá của ta vẫn chưa đúng.
Như vậy, nó quá tốn tài nguyên và xử lý của thiết bị. Để đơn giản hơn thế, ta sẽ bắt sự kiện kết thúc việc edit của UITextField. Và sau đó mới tính chuyện trăm năm sau. Xem đoạn code thay thế sau:
let search = searchCityName.rx.controlEvent(.editingDidEndOnExit) .map { self.searchCityName.text ?? "" } .filter { !$0.isEmpty } .flatMap { text in return WeatherAPI.shared .currentWeather(city: text) .catchErrorJustReturn(Weather.empty) } .asDriver(onErrorJustReturn: Weather.empty)
Khác nhau ở chỗ thay vì dùng rx.text
thì dùng searchCityName.rx.controlEvent(.editingDidEndOnExit)
. Tập trung vào 1 sự kiện của Keyboard mà thôi.
Bạn build project và thử gõ vài chữ search, xong nhấn Return để thấy kết quả.
Lần này, lúc ta đang gõ chữ thì Label City Name không có thay đổi giá trị. Cũng như API sẽ không được gọi. Lúc nào, nhấn Return ở keyboard thì mọi thứ mới hoạt đông. Và dữ liệu sẽ về.
6. Binding Property
Ngoài việc sử dụng các ControlProperty, chúng ta có thể tạo các BindingProperty để binding dữ liệu. Đó là các Binder.
Đây là class để tạo ra các đối tượng mang tính chất là ObserverType. Nhằm để giúp việc
bind
data của một Observable.
Phần này, mình đã đề cập ở phần trước. Bạn có thể tìm đọc lại nó nếu chưa biết cách sử dụng. Và ưu điểm của nó thì giúp bạn custom một cách nhanh chóng cho các Property của UI Control.
Tạm kết
- Driver giúp bạn đưa dữ liệu lên UI Control mà không cần lo lắng gì tới Main Thread hoặc lỗi. Thích hợp với Model State.
- Signal tương tự như Driver. Nhưng sẽ không lưu trữ bộ đệm. Thích hợp với Model Event.
- ControlProperty là một phần của Observable/ObservableType. Nó đại diện cho các property của các thành phần UI.
- ControlEvent là một phần của Observable/ObservableType. Nó đại diện cho các sự kiện của các thành phần UI.
- Chúng ta có thể tạo các BindingProperty để binding dữ liệu. Đó là các Binder.
Okay! Tới đây thì mình xin kết thúc bài viết RxCocoa Traits này. Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.
Cảm ơn bạn đã đọc bài viết này!
Tham khảo:
- RxCocoa Traits: https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md#rxcocoa-traits
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)