RxSwift course - transformations: map, filter, compactMap, scan, reduce

Photo by Gabriel Beaudry / Unsplash

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:

RxSwift course
I’ll put here all RxSwift related posts! RxSwift course - basicsFor 3 years I’m working with RxSwift on daily basis. It helps a lot with data manipulation and UI binding when MVVM is your architecture of choice. The framework gives developers flexibility and extendability. I made even an Rx
Artur Gruchała

Artur Gruchała

I started learning iOS development when Swift was introduced. Since then I've tried Xamarin, Flutter, and React Native. Nothing is better than native code:)
Poland