Contents
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:
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à 1Tuple
, do sự kết hợp thêmindex
từ nó- Qua lại em toán tử
map
để tính toán và biến đổi nó về lại 1Int
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ùngmessage
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ộcObservable
- 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ềustream
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ệcsubcirbe
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 StructUser
trên. Chúng cần phải khởi tạomessage
của nó. Và kiểu dữ liệu củamessage
là 1BehaviorSubject
. Tức là thuộc tính này phát đc dữ liệu đisubject
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áccuTy
&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ácevent
của Observable thành mộtelement
.
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ácevent
là error dematerialize
chuyển đổi các giá trị là event.next
thành cácelement
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ácevents
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!
Related Posts:
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)