This time we will take a look at transformations. RxSwift provides many ways to manipulate observable's values. I'll show you a couple of basic ones. I'll use the same starter project.
Map
Map operator works exactly like the one form Swift collection types. It changes the current value into a new value. You can map it to a new type or just modify the existing value, like multiplying or adding a line break at the end of the string.
let disposeBag = DisposeBag()
let subject = PublishSubject<Int>()
subject
.map { Character(UnicodeScalar($0)!) }
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
subject.onNext(36)
subject.onNext(45)
subject.onNext(76)
This code will map Int to character. Output is simple but shows the idea behind the map.
$
-
L
Filter
The filter ensures, that only values that are matching the predicate will be emitted.
let disposeBag = DisposeBag()
let subject = PublishSubject<Int>()
subject
.filter { $0 % 2 == 0 }
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
subject.onNext(36)
subject.onNext(45)
subject.onNext(76)
subject.onNext(50)
subject.onNext(22)
subject.onNext(113)
Code above will ensure only even numbers are emitted:
36
76
50
22
Scan and reduce
Scan and reduce are accumulators. You provide seed value (fancy name for initial value) and accumulator - block of code that will join previous accumulated value with newly emitted. These two operators differ when it comes to emitting accumulated result:
- scan emits accumulated value every time.
- reduce waits for observable to complete, then it emits one final value and completes - reducing all emitted elements into one result.
Consider this code:
import Foundation
import RxSwift
let disposeBag = DisposeBag()
let subject = PublishSubject<Int>()
subject
.scan(1.0) { seed, value in Double(value) * seed }
.subscribe(onNext: { print("Scan value \($0)") })
.disposed(by: disposeBag)
subject
.reduce(0, accumulator: +)
.subscribe(onNext: { print("Reduce value \($0)") })
.disposed(by: disposeBag)
subject.onNext(1)
subject.onNext(2)
subject.onNext(3)
subject.onNext(4)
subject.onNext(5)
subject.onNext(6)
subject.onNext(7)
And output:
Scan value 1.0
Scan value 2.0
Scan value 6.0
Scan value 24.0
Scan value 120.0
Scan value 720.0
Scan value 5040.0
No reduce value is emitted! We forgot to send the completed
event to our subject. Add subject.onCompleted()
at the end and rerun the code, now reduce value is emitted:
Scan value 1.0
Scan value 2.0
Scan value 6.0
Scan value 24.0
Scan value 120.0
Scan value 720.0
Scan value 5040.0
Reduce value 28
compactMap
This operator emits only if the transformation doesn't return nil
. In our example, it will emit a new value only if a string can be represented by the URL
:
let disposeBag = DisposeBag()
let subject = PublishSubject<String>()
subject
.compactMap { URL(string: $0) }
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
subject.onNext("google.com")
subject.onNext("https://arturgruchala.com")
subject.onNext("not\\an\\url")
subject.onNext("suprisingly/an/url")
And produced output:
google.com
https://arturgruchala.com
suprisingly/an/url
Bonus - skipping nils!
As a bonus, we will create our own operator, skipNil
. It is very easy:
extension Observable {
func skipNil<Result>() -> Observable<Result> where Element == Result? {
compactMap{ $0 }
}
}
let disposeBag = DisposeBag()
let subject = PublishSubject<String?>()
subject
.skipNil()
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
subject.onNext(nil)
subject.onNext("one")
subject.onNext(nil)
subject.onNext("two")
subject.onNext(nil)
subject.onNext(nil)
subject.onNext(nil)
subject.onNext("three")
subject.onNext(nil)
subject.onNext(nil)
With the power of swift generic types, we can add extension with function that will work only for optional elements. The output shows filtered nil values:
one
two
three
And this is it for this time, stay tuned for more RxSwift materials coming soon!
Here you can find all RxSwift course posts: