Improvements of flatMap function in RxSwift
How to be happy with RxSwift and many closures
Hey! If you read it, you probably good in RxSwift and ARC , understand differences between weak and unowned keywords. If no, please get acquainted with it and come back!
In my first post I want to tell you about my flatMap
improvements. I start immediately with problem…
For example we have two requests: Login and LoadPosts. After login I want to get posts for current user:
login(username: "Username", password: "Password")
.flatMap { [weak self] user -> Observable<[Post]> in
return self.loadPosts(for: user)
}
.subscribe()
So, here compiler will swear us, that we have to use !
and ?
for self. Of course, you may say: “Why don’t we use unowned
keyword?” Yes, we can, but I think it’s wrong to use it in that case.
The unowned or weak discussion boils down to a question of lifetime of the variable and the closure that references it.
So, there is a big article about choosing between unowned and weak: https://www.uraimo.com/2016/10/27/unowned-or-weak-lifetime-and-performance/
Weak , I Choose You! Now you probably want to change previously code to this one:
login(username: "Username", password: "Password")
.flatMap { [weak self] user -> Observable<[Post]> in
guard let `self` = self else {
return Observable.empty()
}
return self.loadPosts(for: user)
}
.subscribe()
but when you have many flatMap
functions it looks so messy — we don’t want to see messy code! Only elegant, beautiful code!
It’s time for an awesome extension:
extension ObservableType {
func flatMap<A: AnyObject, O: ObservableType>(weak obj: A, selector: @escaping (A, Self.E) throws -> O) -> Observable<O.E> {
return flatMap { [weak obj] value -> Observable<O.E> in
try obj.map { try selector($0, value).asObservable() } ?? .empty()
}
}
}
and now our requests look so:
login(username: "Username", password: "Password")
.flatMap(weak: self) { obj, user -> Observable<[Post]> in
return obj.loadPosts(for: user)
}
.subscribe()
There is a full snippet for all flatMap
functions:
extension ObservableType {
func flatMap<A: AnyObject, O: ObservableType>(weak obj: A, selector: @escaping (A, Self.E) throws -> O) -> Observable<O.E> {
return flatMap { [weak obj] value -> Observable<O.E> in
try obj.map { try selector($0, value).asObservable() } ?? .empty()
}
}
func flatMapFirst<A: AnyObject, O: ObservableType>(weak obj: A, selector: @escaping (A, Self.E) throws -> O) -> Observable<O.E> {
return flatMapFirst { [weak obj] value -> Observable<O.E> in
try obj.map { try selector($0, value).asObservable() } ?? .empty()
}
}
func flatMapWithIndex<A: AnyObject, O: ObservableType>(weak obj: A, selector: @escaping (A, Self.E, Int) throws -> O) -> Observable<O.E> {
return flatMapWithIndex { [weak obj] value, index -> Observable<O.E> in
try obj.map { try selector($0, value, index).asObservable() } ?? .empty()
}
}
func flatMapLatest<A: AnyObject, O: ObservableType>(weak obj: A, selector: @escaping (A, Self.E) throws -> O) -> Observable<O.E> {
return flatMapLatest { [weak obj] value -> Observable<O.E> in
try obj.map { try selector($0, value).asObservable() } ?? .empty()
}
}
}
Hope you enjoyed this article! Feel free to comment if you have any question, feedback or best practices!
I during ITC build processing
Shaggy iOS Engineer at Rosberry. Reactive, Open-Source lover and Retain-cycle detector
Also check my telegram channel about programming, twitter and my open-source projects:
Hi Nikita,
This is a get extension that you wrote 👍🏻👍🏻👍🏻👍🏻
However can you help me out with writing the extension for map?