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 September 23, 2020

RxSwift – Transforming Operators

RxSwift

Contents

  • Chuẩn bị
  • Transforming Operators
  • 1. Transforming elements
    • 1.1. toArray
    • 1.2. map
  • 2. Transforming inner observables
    • 2.1. flatMap
      • Demo code
    • 2.2. flatMapLastest
      • Demo code
  • 3. Observing events
    • 3.1. Error
    • 3.2. materialize
    • 3.3. dematerialize
  • Tạm kết

Chào bạn đến với Fx Studio. Chúng ta lại tiếp tục với bài viết về loại các toán tử trong RxSwift. Bài viết lần này với chủ đề là Transforming Operators.

Nếu bạn bỏ sót hay chưa đọc về nhóm toán tử được trình bài ở bài viết trước đó, thì có thể tham khảo link sau:

    • RxSwift – Filtering Operators

Okay! mọi việc đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

    • Xcode 11
    • Swift 5
    • Playground

Vẫn là em Playground huyền thoại. Chúng ta vẫn còn dùng tới nó để demo code cho bài viết này. Bạn chỉ cần tạo mới 1 file Playground từ project mà đã cài đặt ngay từ bài đầu của series. Bạn có thể checkout code lại đây.

Transforming Operators

Về Transforming Operators, chúng nó là các nhóm toán tử được sử dụng để biến đổi dữ liệu phát ra từ Observable, nhằm phù hợp với các yêu cầu từ Subscriber.

Phần này bao gồm những Operators quan trọng nhất của RxSwift. Nó tập trung vào việc biến đổi.

  • Biến đổi từ kiểu dữ liệu này thành kiểu dữ liệu khác
  • Biến đổi Observable sequence này thành Observable sequence khác
  • …

Trong Swift, cũng có các function tương tự như map & flatMap. Nếu bạn đã biết tới nó rồi thì các kiến thức đó sẽ giúp bạn nhiều trong phần này.

1. Transforming elements

1.1. toArray

Việc nhận từng phần tử một thì rất tốn thời gian và đôi khi bạn muốn nhận hết một lèo tất cả phần tử của 1 Observable. Thì hãy dùng toán tử này toArray(). Nó sẽ giúp bạn gom tất cả phần tử được phát đi thành 1 array. Việc này sẽ thực hiện sau khi Observable kết thúc.

Xem đoạn code ví dụ sau:

    let bag = DisposeBag()
    
    Observable.of(1, 2, 3, 4, 5, 6)
        .toArray()
        .subscribe(onSuccess: { value in
            print(value)
        })
        .disposed(by: bag)

Thực thi code trên, bạn sẽ thấy giá trị nhận được là một Array Int. Điểm đặc biệt ở đây là với toán tử toArray thì nó sẽ biến đổi Observable đó về thành là 1 Single. Khi đó chỉ cho phép là  .onSuccess hoặc .error mà thôi.

1.2. map

Chúng ta xem qua hình minh hoạ cho toán tử map ở dưới đây.

Hình mình hoạ việc biến đổi từng giá trị phát ra với luật nhân đôi giá trị của phần tử. Và qua trên chắc bạn cũng hiểu được phần nào ý nghĩa của toán tử này rồi.

Về toán tử map , nó thuộc hạng thần thánh nhất rồi. Xuất hiện nhiều ở nhiều ngôn ngữ và ý nghĩa không thay đổi. Khi sử dụng toán tử này thì có một số đặc điểm sau

  • Biến đổi từ kiểu dữ liệu này thành kiểu dữ liệu khác cho các phần tử nhận được
  • Việc biến đổi được xử lý bằng một closure
  • Sau khi biến đổi nếu bạn subscribe thì hãy chú ý tới kiểu dữ liệu mới đó, tránh bị nhầm lẫn.

Xem ví dụ code thôi!

    let bag = DisposeBag()
    
    let formatter = NumberFormatter()
    formatter.numberStyle = .spellOut
    
    Observable<Int>.of(1, 2, 3, 4, 5, 10, 999, 9999, 1000000)
        .map { formatter.string(for: $0) ?? "" }
        .subscribe(onNext: { string in
            print(string)
        })
        .disposed(by: bag)

Thực thi code như sau:

one
two
three
four
five
ten
nine hundred ninety-nine
nine thousand nine hundred ninety-nine
one million

Đoạn code trên sẽ biến đổi các giá trị là Int thành String và hàm biến đổi được thực hiện dựa vào một đối tượng NumberFormatter dùng để đọc/phát âm các số đó (theo ngôn ngữ nào đó).

Thật là ảo diệu phải không nào.

Thử tiếp với toán tử enumerated kết hợp với map xem như thế nào. Ví dụ như sau:

    let disposeBag = DisposeBag()
    
    Observable.of(1, 2, 3, 4, 5, 6)
        .enumerated()
        .map { index, integer in
            index > 2 ? integer * 2 : integer
        }
        .subscribe(onNext: { print($0) })
        .disposed(by: disposeBag)

Đây là một pha bẻ lái đi vào lòng người.

  • Từ một Observable với Int là kiểu dữ liệu cho các phần tử được phát đi
  • enumerated biến đổi Observable đó với kiểu dữ liệu giờ là 1 Tuple, do sự kết hợp thêm index từ nó
  •  Qua lại em toán tử map để tính toán và biến đổi nó về lại 1 Int
  • subscribe thì như bình thường

Đây là cách bạn lấy được index của các phần tử rồi sau đó biến chúng về lại kiểu giá trị ban đầu.

Rất chi là có ý nghĩa nếu bạn muốn có một for loop với Rx.

2. Transforming inner observables

Đây là nhóm toán tử phức tạp nhất trong Transforming Operators. Để sử dụng làm ví dụ cho các ví dụ tiếp theo, thì mình sẽ khai báo 1 struct như sau:

struct User {
    let message: BehaviorSubject<String>
}

Trong đó:

  • User là tên struct để tạo ra những đối tượng người dùng
  • message là thông điệp mà đối tượng nào đó muốn phát đi

OKAY! bắt đầu loạn não thôi.

2.1. flatMap

Tên của nó có nghĩa là làm phẳng. Vậy là làm phẳng những gì? À, trước tiên xem qua sơ đồ ví dụ nó trước nha.

Lần này chúng ta có 1 toán tử, sẽ biến đổi nhiều Observable thành 1 Observable. Mà các Observable đó là các giá trị do 1 Observable lớn hơn phát đi. Ta có thể mô tả qua như sau:

  • Ta có 1 Observable gốc có kiểu dữ liệu cho các element của nó là 1 kiểu thuộc Observable
  • Vì các element có kiểu Observable ,thì nó có thể phát dữ liệu. Lúc này, chúng ta có rất nhiều stream phát đi
  • Muốn nhận được tất cả dữ liệu từ tất cả các element của Observable gốc, thì ta dùng toán tử flatMap
  • Chúng sẽ hợp thể tất cả các giá trị của các element đó phát đi thành 1 Observable ở đầu cuối. Mọi công việc subcirbe vẫn bình thường và ta không hề hay biết gì ở đây.

Demo code

Chúng ta sẽ đi vào ví dụ cho dễ hiểu nào:

    let bag = DisposeBag()
    
    let cuTy = User(message: BehaviorSubject(value: "Cu Tý chào bạn!"))
    let cuTeo = User(message: BehaviorSubject(value: "Cu Tèo chào bạn!"))
    
    let subject = PublishSubject<User>()

Bắt đầu với việc khởi tạo các đối tượng

  • bag thì khỏi nói rồi. Túi rác quốc dân huyền thoại.
  • cuTy & cuTeo là 2 đối tượng của Struct User trên. Chúng cần phải khởi tạo message của nó. Và kiểu dữ liệu của message là 1 BehaviorSubject. Tức là thuộc tính này phát đc dữ liệu đi
  • subject là nơi thực hiện toán từ flatMap

Cuộc vui giờ mới bắt đầu. Tiến hành sử dụng flatMap nào:

    subject
        .flatMap { $0.message }
        .subscribe(onNext: { msg in
            print(msg)
        })
        .disposed(by: bag)

Ta sử dụng flatMap để biến đổi từ User về là String , vì kiểu dữ liệu phát đi của thuộc tính message là String. Tiếp theo tiến hành phát tín hiệu.

    // subject
    subject.onNext(cuTy)
    
    // cuTy
    cuTy.message.onNext("Có ai ở đây không?")
    cuTy.message.onNext("Có một mình mình thôi à!")
    cuTy.message.onNext("Buồn vậy!")
    cuTy.message.onNext("....")
    
    // subject
    subject.onNext(cuTeo)
    
    // cuTy
    cuTy.message.onNext("Chào Tèo, bạn có khoẻ không?")
    
    // cuTeo
    cuTeo.message.onNext("Chào Tý, mình khoẻ. Còn bạn thì sao?")
    
    // cuTy
    cuTy.message.onNext("Mình cũng khoẻ luôn")
    cuTy.message.onNext("Mình đứng đây từ chiều nè")
    
    // cuTeo
    cuTeo.message.onNext("Kệ Tý. Ahihi")

Bạn hãy chú ý:

  • Có 3 đối tượng luân phiên nhau phát dữ liệu
  • subject đóng vài trò là Observable gốc, sẽ phát đi các dữ liệu là các Observable khác
  • cuTy & cuTeo sẽ phát đi mà String

Với flatMap thì chúng sẽ được gom lại thành 1 Observable và các subscriber sẽ nhận được trọn vẹn. Thực thi như sau:

Cu Tý chào bạn!
Có ai ở đây không?
Có một mình mình thôi à!
Buồn vậy!
....
Cu Tèo chào bạn!
Chào Tèo, bạn có khoẻ không?
Chào Tý, mình khoẻ. Còn bạn thì sao?
Mình cũng khoẻ luôn
Mình đứng đây từ chiều nè
Kệ Tý. Ahihi

2.2. flatMapLastest

Trong nhóm toán tử Transforming Operators này, thì toán tử flatMapLastest thuộc dạng hại não nhiều nhất. Về định nghĩa đơn giản nhất thì như sau:

map + switchLatest = flatMapLatest

Về cơ bản là giống như flatMap về việc hợp nhất các Observable lại với nhau. Tuy nhiên, điểm khác là nó sẽ chỉ phát đi giá trị của Observable cuối cùng tham gia vào. Ví dụ

  • Có 3 Observable là O1, O2 và O3 join vào lần lượt
  • flatMapLatest sẽ biến đổi các Observable đó thành 1 Observable duy nhất
  • Giá trị nhận được tại một thời điểm chính ta giá trị của phần tử cuối cùng join vào lúc đó

Demo code

Khó hiểu phải không nào, xem code ví dụ thôi!

    let bag = DisposeBag()
    
    let cuTy = User(message: BehaviorSubject(value: "Tý: Cu Tý chào bạn!"))
    let cuTeo = User(message: BehaviorSubject(value: "Tèo: Cu Tèo chào bạn!"))
    
    let subject = PublishSubject<User>()
    
    subject
        .flatMapLatest { $0.message }
        .subscribe(onNext: { msg in
            print(msg)
        })
        .disposed(by: bag)
    
    // subject
    subject.onNext(cuTy)
    
    // cuTy
    cuTy.message.onNext("Tý: Có ai ở đây không?")
    cuTy.message.onNext("Tý: Có một mình mình thôi à!")
    cuTy.message.onNext("Tý: Buồn vậy!")
    cuTy.message.onNext("Tý: ....")
    
    // subject
    subject.onNext(cuTeo)
    
    // cuTy
    cuTy.message.onNext("Tý: Chào Tèo, bạn có khoẻ không?")
    
    // cuTeo
    cuTeo.message.onNext("Tèo: Chào Tý, mình khoẻ. Còn bạn thì sao?")
    
    // cuTy
    cuTy.message.onNext("Tý: Mình cũng khoẻ luôn")
    cuTy.message.onNext("Tý: Mình đứng đây từ chiều nè")
    
    // cuTeo
    cuTeo.message.onNext("Tèo: Kệ Tý. Ahihi")

À, mà cũng là ví dụ với flatMap thôi. Chỉ thay flatMapLatest cho flatMap và thêm chút vào message cho dễ phân biệt. Xem kết quả thực thi trước.

Tý: Cu Tý chào bạn!
Tý: Có ai ở đây không?
Tý: Có một mình mình thôi à!
Tý: Buồn vậy!
Tý: ....
Tèo: Cu Tèo chào bạn!
Tèo: Chào Tý, mình khoẻ. Còn bạn thì sao?
Tèo: Kệ Tý. Ahihi

Sau khi, cuTeo join vào thì cuTy nói chuyện. Tuy nhiên, subject không nhận được tín hiệu gì từ nó. Buồn thật!

Hi vọng qua ví dụ trên bạn đã hiểu được phần nào về flatMapLatest. Còn nó có rất nhiều trường hợp có thể áp dụng vào. Nhưng chúng ta tạm thời dừng tại đây với em nó thôi.

3. Observing events

3.1. Error

Với 2 toán tử trên của Transforming Operators, bạn có suy nghĩ nếu chúng phát đi error hay completed thì sẽ như thế nào. Sẽ sụp đổ tất cả hay có gì khác nữa … Nhưng chắc một điều rằng, bạn không thể nào quản lý được hết tất cả chúng. Nhất là khi các Observable đó là 1 thuộc tính của một đối tượng khác.

Sử dụng lại ví dụ trên với 2 người bạn cuTeo & cuTy để xem thử lỗi sẽ như thế nào:

    enum MyError: Error {
      case anError
    }
    
    let bag = DisposeBag()
    
    let cuTy = User(message: BehaviorSubject(value: "Tý: Cu Tý chào bạn!"))
    let cuTeo = User(message: BehaviorSubject(value: "Tèo: Cu Tèo chào bạn!"))
    
    let subject = PublishSubject<User>()
    
    let roomChat = subject
        .flatMapLatest { $0.message }
        
     roomChat
        .subscribe(onNext: { msg in
            print(msg)
        })
        .disposed(by: bag)
    
    subject.onNext(cuTy)
    
    cuTy.message.onNext("Tý: A")
    cuTy.message.onNext("Tý: B")
    cuTy.message.onNext("Tý: C")
    
    cuTy.message.onError(MyError.anError)
    cuTy.message.onNext("Tý: D")
    cuTy.message.onNext("Tý: E")
    
    subject.onNext(cuTeo)
       
    cuTeo.message.onNext("Tèo: 1")
    cuTeo.message.onNext("Tèo: 2")

Bạn chú ý tới việc cuTy phát ra 1 onError nha. Thực thi đoạn code trên và xem kết quả như thế nào:

Tý: Cu Tý chào bạn!
Tý: A
Tý: B
Tý: C
Unhandled error happened: anError

Vâng, toàn bộ code đã bị sụp đổ. Chúng ta không thể nào handle được các Observable như cuTy phát đi error hay can thiệp vào property của nó. Để giải quyết chúng nó thì ta có cách sau.

3.2. materialize

Bạn hãy sử dụng lại đoạn code sau với việc thêm toán tử materialize.

    let roomChat = subject
        .flatMapLatest {
            $0.message.materialize()
        }

Xem kết quả như thế nào:

next(Tý: Cu Tý chào bạn!)
next(Tý: A)
next(Tý: B)
next(Tý: C)
error(anError)
next(Tèo: Cu Tèo chào bạn!)
next(Tèo: 1)
next(Tèo: 2)

Chúng ta đã nhận hết các phần tử có thể nhận, kể cả error. Tuy nhiên, có điều lạ ở đây. Nó chính là việc thay vì nhận các giá trị của .next, bây giờ bất cứ event nào cũng sẽ bị biến thành giá trị hết.

(Sơ đồ mình hoạ cho toán tử materialize.)

Và khi trỏ vào roomChat và nhấn giữ Option + click chuột thì bạn sẽ thấy kiểu dữ liệu của nó là Observable<Event<Int>>.

Như vậy, toán tử materialize sẽ biến đổi các event của Observable thành một element.

3.3. dematerialize

(Sơ đồ mình hoạ cho toán tử dematerialize.)

Để biến đổi ngược lại thì như thế nào? Vì chúng ta cần giá trị chứ ko cần event. Trái ngược với toán tử trên thì  dematerialize sẽ biến đổi các event và tách ra các element.

Nói nôm na là bạn có để lấy lại được giá trị từ nó. Edit lại đoạn code ví dụ lúc subscribe đó.

     roomChat
        .filter{
            guard $0.error == nil else {
                print("Lỗi phát sinh: \($0.error!)")
                return false
            }
            
            return true
        }
        .dematerialize()
        .subscribe(onNext: { msg in
            print(msg)
        })
        .disposed(by: bag)

Trong đó:

  • Dùng filter để lọc đi các event là error
  • dematerialize chuyển đổi các giá trị là event .next thành các element
  • subscribe như bình thường

Thực thi code thì bạn sẽ thấy điều kì diệu:

Tý: Cu Tý chào bạn!
Tý: A
Tý: B
Tý: C
Lỗi phát sinh: anError
Tèo: Cu Tèo chào bạn!
Tèo: 1
Tèo: 2

Vừa nhận được giá trị, vừa có thể bắt được error nữa. Ahihi!

 

Mình xin hết thúc bài viết về nhóm toán tử Transforming Operators tại đâ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. Hẹn gặp lại bạn ở các phần tiếp theo của các nhóm toán tử trong RxSwift.

Tạm kết

Mình sẽ tóm tắt lại ý nghĩa các toán tử trong nhóm Transforming Operators nha.

  • toArray đưa tất cả phần tử được phát ra của Observavle thành 1 array. Bạn sẽ nhận được array đó khi Observable kia kết thúc.
  • map toán tử huyền thoại với mục đích duy nhất là biến đổi kiểu dữ liệu này thành kiểu dữ liệu khác.
  • flatMap làm phẳng các Observables thành 1 Observable duy nhất. Và các phần tử nhận được là các phần tử từ tất cả các Observables kia phát ra. Không phân biệt thứ tự đăng ký.
  • flatMapLatest tương tư cái flatMap thôi. Nhưng điểm khác biệt ở chỗ chỉ nhận giá trị được phát đi của Observable cuối cùng tham gia vào.
  • materialize thay vì nhận giá trị được phát đi. Nó biến đổi các events thành giá trị để phát đi. Lúc này, error hay completed thì cũng là 1 giá trị mà thôi.
  • dematerialize thì ngược lại materialize. Giải nén các giá trị là events để lấy giá trị thật sự trong đó.

 

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares8

Related Posts:

  • RxSwift - Creating an Observable
    RxSwift - Creating an Observable
  • RxSwift - Tìm hiểu Operators & Hello world!
    RxSwift - Tìm hiểu Operators & Hello world!
  • RxSwift - Traits
    RxSwift - Traits
  • RxSwift vs. UIKit - Hello ViewController
    RxSwift vs. UIKit - Hello ViewController
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:

  • RxSwift vs. UIKit – Tương tác giữa các ViewController
    RxSwift vs. UIKit – Tương tác giữa các ViewController
  • RxSwift vs. UIKit - Fetching Data from API
    RxSwift vs. UIKit - Fetching Data from API
  • RxSwift vs. UIKit - Hello ViewController
    RxSwift vs. UIKit - Hello ViewController
  • RxSwift - Publish Subjects
    RxSwift - Publish Subjects
  • RxSwift - Phần 1 : Cơ bản
    RxSwift - Phần 1 : Cơ bản

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!