Hôm nay mình sẽ giới thiệu về Flatmap trong rxswift. Định nghĩa và sử dụng khi nào.
RxSwift hỗ trợ rất nhiều toán tử FlatMap.

  • FlatMap
  • FlatMapLastest
  • FlatMapFirst
  • FlatMapWithIndex

Khái niệm

FlatMap

Flatmap gần giống với map tức lặp qua tất cả các signal mà Obserable phát ra.
Tuy nhiên flatMap chuyển đổi mỗi phần tử của một Observable gốc thành một Observable mới, sau đó kết hợp tất cả các Observable mới thành một Observable
Có thể hiểu như sau: Mỗi tín hiệu phát ra từ Observable gốc vừa là tham số đầu vào, vừa là trigger của một Observable mới. Kết quả là một tập hợp các Observable, sau đó các Observable đó sẽ được kết hợp(merge) thành một Observable duy nhất.
Vì vậy mà Flatmap rất hữu ích:
Ví dụ.
Nếu bạn không sử dụng Flatmap:
Button với sự kiện tap lắng nghe dữ liệu trả về từ API. Rồi sau đó update lại data.

button.rx.tap
    .subscribe(onNext: { [weak self] in
        API.fetchData
            .subscribe(onNext: { data in
                self?.updateUI(with: data)
            })
            .disposed(by: self?.disposeBag!)
    })
    .disposed(by: disposeBag)

Chúng ta thấy trên là một obserable sequence lông nhau và khó đề đọc và gọi lại.

Nếu bạn sử dụng Flatmap thì sao:

button.rx.tap
    .flatMap { API.fetchData }
    .subscribe(onNext: { data in
        self?.updateUI(with: data)
    })
    .disposed(by: disposeBag)

Code rất sạch sẽ. Sự kiến button gọi API.fetchData sau đó biến đổi các tín hiệu dữ liệu phát ra thành các obserables có thể quan sát được (có thể .subcrible( ... ) )

FlatMapLastest và FlatMapFirst

Chúng có gì khác giữa 2 toán tử trên với flatmap????
Hãy xem xet ví dụ sử dụng Flatmap

let disposeBag = DisposeBag()

struct Player {
    var level: Variable<Int>
}

// players
let killua = Player(level: Variable(50))
let gon = Player(level: Variable(50))

let player = PublishSubject<Player>()

player.asObservable()
    .flatMap { $0.level.asObservable() }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

player.onNext(killua) // 50
killua.level.value = 51 // 51
player.onNext(gon) //50
gon.level.value = 51 // 51
killua.level.value = 52 // 52 Chú ý giá trị này sẽ được in ra.

Xem ví dụ bạn thấy kết quả in ra có gì bất thường không. Đó là level của killua là 52. Tại sao lại thế ???
Là bởi vì Tất cả các obserable phát ra bởi Flatmap vẫn tiếp tục được subcribed khi chưa bị dispose.
Vì sử dụng flatmap nên killua cũng sẽ được phát ra (subcribed) khi mà gon được subcribed.

Vậy tại sao gon.level vẫn là 51. Bởi vì trước đó gon chưa được đăng kí. vào flatmap.

Hãy xem ví dụ với FLatmapLastest:

player.asObservable()
    .flatMapLatest { $0.level.asObservable() }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

player.onNext(killua) // 50
killua.level.value = 51 // 51
 
player.onNext(gon) //50
gon.level.value = 51 // 51
killua.level.value = 52 // Giá trị này không được in ra từ out put chỉ có: 50 51 50 51

Sự khác biệt ở đây là giá trị cuối cùng sẽ không được in ra ở output vì flatmaplastest đã chuyển sang gon - obserable cuối cùng emitted(phát ra).

Hãy xem ví dụ FlatmapFirst:

player.asObservable()
    .flatMapFirst { $0.level.asObservable() }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

player.onNext(killua) // 50
killua.level.value = 51 // 51
player.onNext(gon) //Level của gon không được in ra
gon.level.value = 51 // Giá trị mới của gon cũng không được in ra
killua.level.value = 52 // 52

Hãy chú ý ở dòng 2,3 từ dưới lên.
Sự khác biệt là do flatmapFirstflatmapFirst chỉ phát ra những giá trị của obserable đầu tiên.

FlatmapWithIndex

let killua = Player(level: Variable(50))
let gon = Player(level: Variable(50))
let bisque = Player(level: Variable(70))

let player = PublishSubject<Player>()
player.asObservable()
    .flatMapWithIndex { player, index -> Observable<Int> in
        if index < 2 {
            return player.level.asObservable()
        }
        return .empty()
    }
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)

player.onNext(killua) // 50
killua.level.value = 51 // 51
player.onNext(gon) //50
gon.level.value = 51 // 51
player.onNext(bisque) // level của bisque không được in ra vì có index > 2 ở đây bisque index = 3.
killua.level.value = 52 // 52
gon.level.value = 52 // 52

Theo mình hiểu đơn giản thì bạn có thể xử lý dữ liệu phát ra thông qua xử lý trên index. Giống như xử lý mảng thông thường

Vậy khi nào sử dụng chúng.

Flatmap thường được sử dụng khi muốn thực hiện một chuỗi Obserable tuần tự

class API

// gọi API async, trả về token nếu login thành công 
func login(username: String, password: String) -> Observable
// gọi API async, trả về danh sách books
func getBooks(token: String) -> Observable

button.rx.tap.asObservable().login("viblo").flatMap { token in 
    getBooks(token)
}.subscribe(onNext: {
    // hiển thị danh sách books
})

end

Ở ví dụ trên, khi user nhấn login button, hàm login sẽ được thực thi và trả về một token, nó sẽ được dùng để làm tham số đầu cho hàm getBooks. Nếu hàm login bị lỗi, thì hàm getBooks sẽ không được gọi.

Vấn đề: Ví dụ nếu bạn đã nhấn button login 1 lần và bạn nhấn thêm vài ba lần nữa thì điều gì xảy ra

FatmapFirst

Nếu ta k sử dụng flapMapFirst chúng ta vẫn có thể nhận được nhiều hơn 2 tín hiệu sau lần nhấn đầu tiên (vì ở đây là các stream riêng biệt, nên được đối xử riêng biệt).

FlatmapLastest

ngược lại với FlatmapFirst là Flatmap trước flatmap cuối cùng phát ra.

Cả 2 flatmapfirst và lastest sẽ chỉ nhận được 1 tín hiệu phát ra.

Tài liệu tham khảo

http://reactivex.io/documentation/operators/flatmap.html
https://medium.com/@shoheiyokoyama/flatmap-operators-in-rxswift-aa33edc33024