Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on November 10, 2020

RxCocoa Basic – Binding Observables

RxSwift

Contents

  • Chuẩn bị
  • 1. Binding Observables là gì?
    • 1.1. Binding Observables
    • 1.2. Observer Type
  • 2. Sử dụng Binding Observables
    • 2.1. Tách nguồn
    • 2.2. Binding to UI Control
  • 3. Binder
    • 3.1. Khái niệm
    • 3.2. Custom Binder
  • Tạm kết

Chào bạn đến với Fx Studio. Bài viết này sẽ trình bày về một khái niệm mới trong series RxSwift & RxCocoa. Đó là Binding Observables. Chúng ta sẽ tiến hành phần tích và mổ xẻ nó trong iOS project.

Để bắt đầu, bạn cần nắm qua cách để hiển thị dữ liệu trong UIKit với RxCocoa. Đây là bài tiếp nối của bài trước. Nếu bạn chưa biết về nó, thì có thể tham khảo ở link dưới.

    • RxCocoa Basic – Display Data

Còn mọi thứ đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

    • Xcode 12
    • Swift 5.3
    • RxSwift 5.0

Tạm thời, bạn chỉ cần chuẩn bị môi trường như vậy là ổn. Chúng ta vẫn sẽ demo trên iOS Project và bạn có thể dùng lại project của bài trước. Bạn sẽ thấy sự tiếp tục trong quán trình tìm hiểu thêm về vũ trị RxSwift này.

1. Binding Observables là gì?

Về khái niệm, Binding không phải là khái niệm mới trong giới lập trình iOS. Tuy nhiên, một điều khá buồn là Apple chưa có đưa ra một định nghĩa về nó cho nền tảng iOS. Còn với MacOS thì đã có từ lâu. Dó đó, hầu hết các dev đều tự có cách riêng của mình để giải quyết bài toán kết nối dữ liệu này.

Với RxSwift, đã cũng cấp các giải pháp đơn giản cho bài toán trên. Chắc bạn có thể đã từng làm rồi hoặc không để ý tới nó trong RxSwift. Nhưng không sao, mình sẽ giải thích chúng đơn giản nhất trong bài viết này.

1.1. Binding Observables

Bắt đầu câu chuyện ngày hôm nay, chúng ta sẽ tìm hiểu về mối quan hệ của 2 thực thể này. Đó là:

binding

Trong đó,

  • Producer : thực thể tạo ra giá trị
  • Consumer : xử lý giá trị từ Producer

Có thể bạn sẽ liên tương tới mối quan hệ giữa Observable và Observer. Thì ở đây nó mang ý nghĩa giới hạn hơn trong xử lý trong code. Đó là Consumer không được phép return về giá trị. Và nếu bạn suy nghĩ về Binding 2 chiều thì hãy để sau nha.

Còn về cơ bản cho áp dụng Binding bằng cách sử dụng bind(to:) của đối tượng Observable tới 1 đối tượng nào đó. Tất nhiên, yêu cầu Consumer phải là kiểu ObserverType.

ObserverType là các thực thể chỉ chấp nhận việc ghi (write-only) dữ liệu và chúng không thể subscribe được.

1.2. Observer Type

Qua trên, lại thêm một định nghĩa mới, đó là ObserverType. Mới nghe qua thì nghe khá mệt mỏi. Mình sẽ không tìm định nghĩa cụ thể cho nó. Nhưng bạn hãy nhớ lại các thực thể Subject đã học trước đây.

Subject = ObserverType + ObservableType

Nó vừa gởi vừa nhận được. Subject lại lưu trữ được dữ liệu nữa. Giúp cho bạn liên kết UI với dữ liệu. Ngoài ra, có thể đăng ký tới để kích hoạt những thực thể khác nếu cần.

Ngoài Subject, thì Relay cũng có thể sử dụng được với bind(to:).

Tóm tắt phần này. Bạn không cần phải xử lý dữ liệu và gán dữ liệu cho 1 đối tượng nào đó thông cái hàm subscribe và closure truyền thống như trước đây nữa. Công việc của bạn sẽ được gói gọn vào bind(to:). Và chỉ có như vậy mà thôi!

2. Sử dụng Binding Observables

Ở trên, ta đã mô tả sơ về mối quan hệ Binding Observables rồi. Muốn dễ hiểu hơn thì hãy demo vào code. Demo này sẽ dùng lại project ở bài trước đó. Nếu bạn nào quên thì có thể truy cập lại đây:

    • Link: checkout
    • Thư mục: /Examples/BasicRxSwift

Chúng ta vẫn làm việc với màn hình WeatherCityViewController và sẽ tiếp nối các công việc trong đó. Và cũng mô tả cho công việc chúng ta sắp thực hiện thì bạn xem qua hình sau:

Chúng ta sẽ tạo ra một nguồn phát dữ liệu duy nhất. Từ đó, dữ liệu sẽ được điều phối tới các UI Control trong giao diện của bạn. Cách điều phối này sẽ khác cách subscribe  . Thay vì handle dữ liệu và gán cho cách thuộc tính của UI Control, thì chúng ta sẽ bind tới thẳng các thuộc tính của UI Control.

Việc hiển thị dữ liệu mới lên UI Control sẽ là tự động.

2.1. Tách nguồn

Ở trên, đã đề cập tới nguồn dữ liệu. Nên việc trước tiên cần làm là bạn tách nguồn dữ liệu. Và nguồn dữ liệu chính là kết quả nhận được từ API. API sẽ trả về một Observable, đó cũng là tất cả chúng ta cần lúc này.

Bạn hãy mở file WeatherCityViewController và truy cập tới function viewDidLoad. Tìm tới đoạn code của bài trước

searchCityName.rx.text.orEmpty
            .filter { !$0.isEmpty }
            .flatMap { text in
                return WeatherAPI.shared.currentWeather(city: text).catchErrorJustReturn(Weather.empty)
            }
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { weather in
                self.cityNameLabel.text = weather.cityName
                self.tempLabel.text = "\(weather.temperature) °C"
                self.humidityLabel.text = "\(weather.humidity) %"
                self.iconLabel.text = weather.icon
            })
            .disposed(by: bag)

(Xoá cũng được, thêm code khác cũng được hoặc sửa lại cũng được. Tuỳ ý bạn!)

Bạn hãy chia tách nó ra một tí nào. Bắt đầu, bằng việc tạo một Observable search, phụ trách việc gọi API và phát dữ liệu nhận được từ API lại.

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)

Không có gì mới lạ hết. Xoá đi phần subscribe và thêm 1 dòng .share(replay: 1). Nhắc lại toán tử share, thì nó giúp cho việc toàn vẹn giá trị của một Observable khi có nhiều Subscriber đăng ký tới. Và tiết kiệm tài nguyên lẫn bộ nhớ khi không cần thiết phải gọi API nhiều lần cho nhiều Subscriber.

Toán tử share trên thì còn có ý nghĩa là không cần phải gọi lại việc request API khi có một subscription mới. Chỉ dùng lại kết quả mới nhất mà thôi.

Xong! Chúng ta đã có nguồn dữ liệu. Sang bước tiếp theo nào!

2.2. Binding to UI Control

Chúng ta đã có Observable rồi. Nó đóng vài trò là Producer trong muốn quan hệ này. Nhân vật còn thiếu chính là Consumer. Bạn hãy thêm đoạn code sau vào tiếp.

search.map { $0.cityName }
            .bind(to: cityNameLabel.rx.text)
            .disposed(by: bag)

Trong đó:

  • map dùng để biến đổi cả đối tượng Weather thành 1 String đơn giản mà thôi
  • bind để đưa dữ liệu kia tới đúng đối tượng cần tới
  • text nằm trong không gian của UILable.rx, nó là 1 kiểu ObserverType

Bạn hãy truy lùng nó trong thư viện xem text là ai?

public var text: RxCocoa.Binder<String?> { get }

Lại thêm 1 khái niệm mới là Binder. Tạm thời để sau nha. Build app và test thử xem việc bind đầu tiên đã ổn chưa. Còn nếu đã ổn rồi thì bạn quất luôn cho các UI còn lại.

        search.map { "\($0.temperature) °C" }
            .bind(to: tempLabel.rx.text)
            .disposed(by: bag)
        
        search.map { $0.cityName }
            .bind(to: cityNameLabel.rx.text)
            .disposed(by: bag)
        
        search.map { "\($0.humidity) %" }
            .bind(to: humidityLabel.rx.text)
            .disposed(by: bag)
        
        search.map { $0.icon }
            .bind(to: iconLabel.rx.text)
            .disposed(by: bag)

Cũng không quá phức tạp nhỉ! Bạn tiếp tục build và test chúng xem dữ liệu đã hiển thị đầy đủ hết chưa.

3. Binder

3.1. Khái niệm

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

Trong không gian ReactiveX (Rx) của RxCocoa, thì ta có nhiều thuộc tính cho nhiều UI Control mang kiểu Binder này. Nó sẽ giúp liên kết chặt chẽ giữa property UI với dữ liệu của nguồn phát. Phản ứng lại với từng giá trị mà nó nhận được.

Ta lấy ví dụ cho UIProgressView, với 1 property Binder như sau:

  public var progress: Binder<Float>

Và code xem dung lượng file được upload:

let progressBar = UIProgressBar()

let uploadFileObs = uploadFile(data: fileData)

uploadFileObs
   .map { sent, totalToSend in
       return sent / totalToSend
    }
   .bind(to: progressBar.rx.progress) 
   .disposed(by: bag)

Khi lượng dung lượng được upload được gởi đi, thì sẽ nhận được giá trị. Tiến hành biến đổi nó thành  Float và bind(to:) tới thuộc tính progress trong không gian Rx. Khi đó về mặt UI, nó sẽ tự cập nhật luôn trên thanh UIProgressView. Mà ta không cần phải xử lý gì nữa.

3.2. Custom Binder

Chúng ta thực hiện Custom Binder một cách đơn giản nhất thôi nha. Chỉ mở rộng thêm class có sẵn và cho nó gia nhập vào không gian của Rx. Công việc ta gôm các bước sau:

Bước 1: Xác định thuộc tính cần liên kết. Hiển nhiên đây là các thuộc tính bình thường của class

  • Ta chọn WeatherCityViewController làm ví dụ cho cuộc vui này.
  • .title là thuộc tính của ViewController đó, hiển thị tên của ViewController trên NavigationBar

Bước 2: Mở rộng không gian .rx cho WeatherCityViewController

  • Không gian rx thì là các phần extension của lớp Reactive. Bạn tạo 1 extension mới cho nó, theo như đoạn code sau
  • Chú ý Base chính là class của chúng ta muốn thêm vào không gian rx
extension Reactive where Base: WeatherCityViewController {
    //...
}

Bước 3: Tạo Binder Property

  • Về tên đặt thì bạn thích tên gì cũng được, ở đây mình chọn tên là title cho hack não
  • Và kiểu dữ liệu là String
extension Reactive where Base: WeatherCityViewController {
    var title: Binder<String> {
        return Binder(self.base) { (vc, value) in
            vc.title = value
        }
    }
}

Giải thích chút:

  • Quan trọng là hành động gì mà bạn cài đặt trong khối lệnh khởi tạo Binder
  • Trong đó
    • Tham số cho Binder(_:) là base. Nghĩa là chính là ViewController của chúng ta
    • Closure cung cấp với 2 tham số
      • target là base, hay lúc này chính là ViewController
      • value là giá trị nhận được khi Observable gọi hàm bind(to:)
    • Thực hiện logic trong closure cho phù hợp

Bước 4: binding to UI

  • Xác định Observable
  • Xử lý dữ liệu phù hợp cho Binder
  • Gọi hàm bind(to:) tới đối tượng Binder vừa tạo
search.map { $0.cityName }
            .bind(to: self.rx.title)
            .disposed(by: bag)

Đoạn code này đặt ở viewDidLoad, cùng với các đoạn code Binding vừa tạo ở phần trên. Bạn hãy build lại project và cảm nhận kết quả thay đổi nha.

Bạn chú ý, sẽ thấy Title của NavigationBar sẽ update theo những gì chúng ta gõ vào từ bàn phím.

Tạm kết

  • Binding Observables là mối quan hệ giữa 2 thực thể
    • Producer : thực thể tạo ra giá trị
    • Consumer : xử lý giá trị từ Producer
  • ObserverType thì chỉ nhận dữ liệu và không phát đi hay return lại bất cứ gì.
  • bind(to:) là toán tử để đưa dữ liệu phát ra từ Observable tới trực tiếp thuộc tính/đối tượng nào có kiểu là ObserverType. Mà không cần xử lý bằng một closure nào hết.
    • Điều kiện yêu cầu là kiểu dữ liệu của phần tử Observable phát đi và kiểu dữ liệu của ObserverType đó phải giống nhau.
  • Binder là class thuộc dạng ObserverType trong RxCocoa. Custom Binder cho thuộc tính của một class, để đưa dữ liệu trực tiếp lên đó. Và sử dụng được hàm bind(to:)

Okay! Tới đây thì mình xin kết thúc bài viết 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!

FacebookTweetPinYummlyLinkedInPrintEmailShares2

Related Posts:

  • RxCocoa Basic – Delegate Proxy
    RxCocoa Basic – Delegate Proxy
  • RxCocoa Basic - Working with multi UI Control
    RxCocoa Basic - Working with multi UI Control
  • RxCocoa Basic – Forward Delegate
    RxCocoa Basic – Forward Delegate
  • Basic TextField - SwiftUI Notes #27
    Basic TextField - SwiftUI Notes #27
Tags: rxswift
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

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • RxCocoa Basic – Traits
    RxCocoa Basic – Traits
  • Basic Grid Layout - SwiftUI Notes #52
    Basic Grid Layout - SwiftUI Notes #52
  • RxCocoa Basic - Working with multi UI Control
    RxCocoa Basic - Working with multi UI Control
  • SwiftUI - Phần 6 : Basic UI Controls
    SwiftUI - Phần 6 : Basic UI Controls
  • RxCocoa Basic – Extend UIKit
    RxCocoa Basic – Extend UIKit

Archives

  • 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)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!