- Trong chapter này, chúng ta sẽ học về một trong những loại operator quan trọng nhất trong RxSwift: Transforming operators.
- Sử dụng trong vô số trường hợp:
- Xử lý data từ một observable để cho subscriber sử dụng.
- Thêm một điều nữa là có sự tương đồng giữa các transforming operator của RxSwift và Swift standard library, ví dụ như:
map(_:)
vàflatMap(_:)
Nội dung:
- Chapter 7: Transforming Operators
- Chapter 8: Transforming Operators in Practice
Bắt đầu với project RxSwiftPlayground
trong thư mục ./Document/ExampleProject/07-transforming-operators/Chapter7/
.
- Observable phát từng phần tử một, tuy nhiên bạn vẫn sẽ cần làm việc với một collection, ví dụ như cần để binding một observable vào một table / collection view.
- Sử dụng
toArray
chuyển từ observable sequence với các element riêng lẻ thành một observable sequence cũng với những element đó nhưng phát ra.next
event chứa một array tới các subscribers.
example(of: "toArray") {
let disposeBag = DisposeBag()
Observable.of(1, 2, 3)
.toArray()
.subscribe(onNext: {
print($0) })
.disposed(by: disposeBag)
}
--- Example of: toArray ---
[1, 2, 3]
map
operator tương tự như hàmmap
trong Swift standard library, khác ở chỗ là nó giành cho observable.
example(of: "map") {
let disposeBag = DisposeBag()
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
Observable<NSNumber>.of(1, 21, 321)
.map {
formatter.string(from: $0) ?? ""
}
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
--- Example of: map ---
one
twenty-one
three hundred twenty-one
- Trong Chapter 5, ta đã biết sử dụng
enumerated
vàmap
kết hợp với các filtering operator.
example(of: "enumerated and map") {
let disposeBag = DisposeBag()
Observable.of(1, 2, 3, 4, 5, 6)
.enumerated()
.map { index, integer in
(index + 1) * integer
}
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
--- Example of: enumerated and map ---
1
4
9
16
25
36
- RxSwift có sẵn một số operator trong
flatMap
family, cho phép ta truy cập vào bên trong một observable và làm việc với các observable properties của nó. - Theo dõi các ví dụ trong phần sau. Đầu tiên add đoạn code này vào
RxSwiftPlayground.swift
:
struct Student {
var score: BehaviorSubject<Int>
}
- Tài liệu về
flatMap
mô tả thế này:Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence.
- Đầu tiên ta có lần lượt các giá trị khởi đầu của elenment O1, O2, O3 là 1, 2 và 3.
- Bắt đầu với O1,
flatMap
nhận object và truy cập vào các giá trị bên trong object và nhân lên 10. Sau đó nó xử lý element vừa được chuyển đổi thành một observable mới (line đầu tiên, phía bên dưới flatMap), observable đó được flattened vào target observable, nơi mà sẽ chuyển các element mới tới các subscriber. - Sau đó, giá trị của property O1 thay đổi thành 4, trên mable diagram chưa thể hiện điều này, nhưng bằng chứng cho sự thay đổi đó là nó được xử lý đưa vào observable có sẵn cho O1 trước đó với giá trị 40, sau đó được flattened vào target observable.
- Tương tự với O2 và O3.
struct Student {
var score: BehaviorSubject<Int>
}
example(of: "flatMap") {
let disposeBag = DisposeBag()
let ryan = Student(score: BehaviorSubject(value: 80))
let charlotte = Student(score: BehaviorSubject(value: 90))
let student = PublishSubject<Student>()
student
.flatMap {
$0.score
}
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
student.onNext(ryan)
student.onNext(charlotte)
ryan.score.onNext(95)
charlotte.score.onNext(100)
}
--- Example of: flatMap ---
80
90
95
100
- Tóm lại,
flatMap
sẽ tiếp tục xử lý thay đổi từ mỗi observable mà nó tạo ra. - Có lúc ta sẽ cần chức năng này và có lúc ta chỉ cần cập nhật element mới nhất trong source observable. Sử dụng
flatMapLatest
.
flatMapLatest
là sự kết hợp giữamap
vàswitchLatest
.switchLatest
sẽ được nhắc đến trọng Chapter 9: Combining Operators, nó sẽ phát những value từ những observable gần nhất và unsubscribe những observable trước đó.- Vậy nên
flatMapLatest
Projects each element of an observable sequence into a new sequence of observable sequences and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.
flatMapLatest
hoạt động giống vớiflatMap
. Điểm khác biệt là nó tự động switch tới observable gần nhất và unsubscribe những cái trước đó.- Nhìn vào diagram, O1 được nhận bởi
flatMapLatest
, nó thay đổi value thành 10, chuyển nó vào observable mới cho phần tử O1, và flatten nó vào target observable. - Sau đó nó
flatMapLatest
nhận 02, nó cũng thực hiện tương tự, switch đến observable của O2 vì giờ nó là observable mới nhất. - Khi giá trị của O1 thay đổi,
flatMapLatest
vẫn sẽ thực hiện việc chuyển đổi, tuy nhiên sau đó nó sẽ bỏ qua kết quả thực hiện. Quá trình này được lặp lại khi O3 được nhận bởiflatMapLatest
, nó sẽ switch đến sequence observable của O3 và bỏ qua O2. Kết quả nhận được là ở target observable chỉ nhận được những element mới nhất từ observable mới nhất.
example(of: "flatMapLatest") {
let disposeBag = DisposeBag()
let ryan = Student(score: BehaviorSubject(value: 80))
let charlotte = Student(score: BehaviorSubject(value: 90))
let student = PublishSubject<Student>()
student
.flatMapLatest {
$0.score
}
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
student.onNext(ryan)
student.onNext(charlotte)
ryan.score.onNext(95)
charlotte.score.onNext(100)
}
--- Example of: flatMapLatest ---
80
90
100
- Có lúc ta cần convert một observable thành một observable của những event của chính nó.
- Một trường hợp phổ biến là khi ta không điều khiển được observerble mà có observable propety, và ta muốn handle error event, tránh việc terminate những sequence bên ngoài khác.
- Thêm vào playground đoạn code sau:
example(of: "materialize and dematerialize") {
enum MyError: Error {
case anError
}
let disposeBag = DisposeBag()
let ryan = Student(score: BehaviorSubject(value: 80))
let charlotte = Student(score: BehaviorSubject(value: 100))
let student = BehaviorSubject(value: ryan)
let studentScore = student
.flatMapLatest {
$0.score
}
studentScore
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
ryan.score.onNext(85)
ryan.score.onError(MyError.anError)
ryan.score.onNext(90)
student.onNext(charlotte)
}
--- Example of: materialize and dematerialize ---
80
85
Unhandled error happened: anError
subscription called from:
- Error trên chưa được handle, vì thế nên
studentScore
observable bị terminate và cả những observable bên ngoàistudent
. - Sử dụng
materialize
operator để bọc mỗi event được phát ra bởi một observable bên trong một observable.
- Đổi
studentScore
thành:
let studentScore = student
.flatMapLatest {
$0.score.materialize()
}
--- Example of: materialize and dematerialize ---
next(80)
next(85)
error(anError)
next(100)
- Option-click vào
studentScore
sẽ thấy nó có kiểuObservable<Event<Int>>
thay vìObservable<Int>
. - Error vẫn sẽ làm cho
studentScore
terminate, tuy nhiên các observable bên ngoài student sẽ không bị, vậy nên khi switch đến student mới, điểm số của student đó sẽ được in ra thành công.
- Vấn đề là giờ ta đang làm việc với event, không phải element như lúc trước. Và bây giờ là lúc chúng ta cần
dematerialize
. Nó giúp convert từ materialized observable về kiểu ban đầu của nó.
- Đổi subscription thành:
studentScore
.filter {
guard let error = $0.error else {
return true
}
print(error)
return false
}
.dematerialize()
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
--- Example of: materialize and dematerialize ---
80
85
anError
100
- Modify the challenge from Chapter 5 to take alpha-numeric characters
- Bắt đầu với project
RxSwiftPlayground
trong thư mục./Document/ExampleProject/07-transforming-operators/challenge/
input.asObservable()
.map { convert($0) }
.filter { $0 != nil }
.unwrap()
.skipWhile { $0 == 0 }
.take(10)
.toArray()
.subscribe(onNext: {
let phone = format($0)
print(dial(phone))
})
.disposed(by: disposeBag)
Dialing Florent (603-555-1212)...
- Fetch top repos and spice up the feed
Quay lại chapter trước Chapter 5-6: Filtering operators and Filtering operators in practice
Đi đến chapter sau Chapter 9-10: Combining operators and Combining operators in practice
Quay lại RxSwiftDiary's Menu
RxSwift: Reactive Programming with Swift