+2

Bài 9. RxSwift – Transforming Operators

1. Transforming elements

1.1. toArray

image.png 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. Ví dụ:

    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

image.png

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

Ví dụ:

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)

Kết quả:

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

Thêm toán tử .enumerated() lấy được index của các phần tử .

2. Transforming inner observables

Phần này sẽ dùng chung struct sau:

struct So {
    let soNguyen: BehaviorSubject<Int>
}

2.1. flatMap

image.png

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

Ví dụ:

        let soDuong = So(soNguyen: BehaviorSubject(value: 1))
        let soAm = So(soNguyen: BehaviorSubject(value: -1))
        
        let subject = PublishSubject<So>()
        
        subject
            .flatMap { $0.soNguyen }
            .subscribe(onNext: { msg in
                print(msg < 0 ? "Số âm: \(msg)": "Số dương: \(msg)")
            })
            .disposed(by: bag)
        
        subject.onNext(soDuong)
        
        
        soDuong.soNguyen.onNext(2)
        soDuong.soNguyen.onNext(3)
        soDuong.soNguyen.onNext(4)
        soDuong.soNguyen.onNext(5)
        
        subject.onNext(soAm)
        
        soDuong.soNguyen.onNext(6)
        
        soAm.soNguyen.onNext(-2)
        
        soDuong.soNguyen.onNext(7)
        soDuong.soNguyen.onNext(8)
        
        soAm.soNguyen.onNext(-3)

Kết quả:

Số dương: 1
Số dương: 2
Số dương: 3
Số dương: 4
Số dương: 5
Số âm: -2
Số dương: 6
Số dương: 7
Số dương: 8
Số âm: -3
  • 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
  • soDuong & soAm sẽ phát đi mà Int

2.2. flatMapLastest

image.png

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 đó

Ví dụ:

        let soDuong = So(soNguyen: BehaviorSubject(value: 1))
        let soAm = So(soNguyen: BehaviorSubject(value: -1))
        
        let subject = PublishSubject<So>()
        
        subject
            .flatMapLatest { $0.soNguyen }
            .subscribe(onNext: { msg in
                print(msg < 0 ? "Số âm: \(msg)": "Số dương: \(msg)")
            })
            .disposed(by: bag)
        
        subject.onNext(soDuong)
        
        
        soDuong.soNguyen.onNext(2)
        soDuong.soNguyen.onNext(3)
        soDuong.soNguyen.onNext(4)
        soDuong.soNguyen.onNext(5)
        
        subject.onNext(soAm)
        
        soDuong.soNguyen.onNext(6)
        
        soAm.soNguyen.onNext(-2)
        
        soDuong.soNguyen.onNext(7)
        soDuong.soNguyen.onNext(8)
        
        soAm.soNguyen.onNext(-3)

Giống như flatMap nhưng giờ chúng ta thay toán tử flatMap thành flatMapLatest và kết quả nhận được:

Số dương: 1
Số dương: 2
Số dương: 3
Số dương: 4
Số dương: 5
Số âm: -1
Số âm: -2
Số âm: -3

Kể từ khi subject.onNext(soAm) thì soDuong dù đã phát ra thêm giá trị 6, 7, 8 nhưng không được in ra kết quả, đó là điểm khác nhau giữa flatMapflatMapLatest

2.3. Error, Completed

Thử với error hay completed

flatMapLatest

*** Error** Ví dụ:

    let soDuong = So(soNguyen: BehaviorSubject(value: 1))
        let soAm = So(soNguyen: BehaviorSubject(value: -1))
        
        let subject = PublishSubject<So>()
        
        subject
            .flatMapLatest { $0.soNguyen }
            .subscribe(onNext: { msg in
                print(msg < 0 ? "Số âm: \(msg)": "Số dương: \(msg)")
            })
            .disposed(by: bag)
        
        subject.onNext(soDuong)
        
        
        soDuong.soNguyen.onNext(2)
        soDuong.soNguyen.onNext(3)
        soDuong.soNguyen.onNext(4)
        soDuong.soNguyen.onNext(5)
        soDuong.soNguyen.onError(MyError.anError)
        
        subject.onNext(soAm)
        
        soDuong.soNguyen.onNext(6)
        
        soAm.soNguyen.onNext(-2)
        
        soDuong.soNguyen.onNext(7)
        soDuong.soNguyen.onNext(8)
        
        soAm.soNguyen.onNext(-3)

Kêt quả:

Số dương: 1
Số dương: 2
Số dương: 3
Số dương: 4
Số dương: 5
Unhandled error happened: anError

Mọi thứ dùng lại ngay khi soDuong.soNguyen.onError(MyError.anError).

Thử tiếp một ví dụ tiếp theo:

        let soDuong = So(soNguyen: BehaviorSubject(value: 1))
        let soAm = So(soNguyen: BehaviorSubject(value: -1))
        
        let subject = PublishSubject<So>()
        
        subject
            .flatMapLatest { $0.soNguyen }
            .subscribe(onNext: { msg in
                print(msg < 0 ? "Số âm: \(msg)": "Số dương: \(msg)")
            })
            .disposed(by: bag)
        
        subject.onNext(soDuong)
        
        
        soDuong.soNguyen.onNext(2)
        soDuong.soNguyen.onNext(3)
        soDuong.soNguyen.onNext(4)
        soDuong.soNguyen.onNext(5)
     
        
        subject.onNext(soAm)
        
        soDuong.soNguyen.onError(MyError.anError)
        
        soDuong.soNguyen.onNext(6)
        
        soAm.soNguyen.onNext(-2)
        
        soDuong.soNguyen.onNext(7)
        soDuong.soNguyen.onNext(8)
        
        soAm.soNguyen.onNext(-3)

Kết quả:

Số dương: 1
Số dương: 2
Số dương: 3
Số dương: 4
Số dương: 5
Số âm: -1
Số âm: -2
Số âm: -3

Vì sử dụng .flatMapLatest, nên khi subject.onNext(soAm) thì lúc soDuong.soNguyen.onError(MyError.anError) đã không còn hiệu lực.

*** Completed** Với Completed , ví dụ soDuong.soNguyen.onCompleted() tất cả những soDuong.soNguyen.onNext(_) sẽ không hoạt động. Còn lại, vẫn hoạt động bình thường.

flatMap

*** Error** Khác với .flatMapLatest, thì khi .onError thì .flatMap sẽ dừng lại luôn. Bạn có thể sửa ở ví dụ để hiểu chi tiết hơn. *** Completed** Với Completed tương tự như .flatMapLatest , ví dụ soDuong.soNguyen.onCompleted() tất cả những soDuong.soNguyen.onNext(_) sẽ không hoạt động. Còn lại, vẫn hoạt động bình thường.

2.4. materialize

image.png

Toán tử materialize sẽ biến đổi các event của Observable thành một element.

Ví dụ:

  let soDuong = So(soNguyen: BehaviorSubject(value: 1))
        let soAm = So(soNguyen: BehaviorSubject(value: -1))
        
        let subject = PublishSubject<So>()
        
        subject
            .flatMapLatest { $0.soNguyen.materialize() }
            .subscribe(onNext: { msg in
               print(msg)
            })
            .disposed(by: bag)
        
        subject.onNext(soDuong)
        
        soDuong.soNguyen.onNext(2)
        soDuong.soNguyen.onNext(3)
        soDuong.soNguyen.onNext(4)
        soDuong.soNguyen.onError(MyError.anError)
        soDuong.soNguyen.onNext(5)
       
        subject.onNext(soAm)
        
        soDuong.soNguyen.onNext(6)

        soAm.soNguyen.onNext(-2)
        
        soDuong.soNguyen.onNext(7)
        soDuong.soNguyen.onNext(8)
        
        soAm.soNguyen.onNext(-3)

Kết quả:

next(1)
next(2)
next(3)
next(4)
error(anError)
next(-1)
next(-2)
next(-3)

flatMapLatest không bị dừng lại khi soDuong.soNguyen.onError(MyError.anError).

2.5. dematerialize

image.png

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 đó.

Ví dụ:

  let soDuong = So(soNguyen: BehaviorSubject(value: 1))
        let soAm = So(soNguyen: BehaviorSubject(value: -1))
        
        let subject = PublishSubject<So>()
        
        let subjectEvent = subject
            .flatMapLatest { $0.soNguyen.materialize() }
        
        subjectEvent.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)
        
        subject.onNext(soDuong)
        
        soDuong.soNguyen.onNext(2)
        soDuong.soNguyen.onNext(3)
        soDuong.soNguyen.onNext(4)
        soDuong.soNguyen.onError(MyError.anError)
        soDuong.soNguyen.onNext(5)
       
        subject.onNext(soAm)
        
        soDuong.soNguyen.onNext(6)
        
        soAm.soNguyen.onNext(-2)
        
        soDuong.soNguyen.onNext(7)
        soDuong.soNguyen.onNext(8)
        
        soAm.soNguyen.onNext(-3)

Kết quả:

1
2
3
4
Lỗi phát sinh: anError
-1
-2
-3

Tham khảo: https://fxstudio.dev/rxswift-transforming-operators/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí