Chapters

Hide chapters

RxSwift: Reactive Programming with Swift

Fourth Edition · iOS 13 · Swift 5.1 · Xcode 11

5. Filtering Operators
Written by Scott Gardner

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Learning a new technology stack is a bit like building a skyscraper. You’ve got to build a solid foundation before you can reach the sky. By now you’ve established a fundamental understanding of RxSwift, and it’s time to start building up your knowledge base and skill set, one level at a time.

This chapter will teach you about RxSwift’s filtering operators you can use to apply conditional constraints to emitted events, so that the subscriber only receives the elements it wants to deal with. If you’ve ever used the filter(_:) method in the Swift standard library, you’re already half way there. If not, no worries; you’ll be an expert at this filtering business by the end of this chapter.

Getting started

The starter project for this chapter is named RxPlayground. After running ./bootstrap.sh in the project folder, Xcode will open. Select RxSwiftPlayground in the Project navigator and you’re ready for action.

Ignoring operators

You’re going to jump right in and look at some useful filtering operators in RxSwift, beginning with ignoreElements. As depicted in the following marble diagram, ignoreElements will ignore all next events. It will, however, allow stop events through, such as completed or error events.

example(of: "ignoreElements") {
  // 1
  let strikes = PublishSubject<String>()

  let disposeBag = DisposeBag()

  // 2
  strikes
    .ignoreElements()
    .subscribe { _ in
      print("You're out!")
    }
    .disposed(by: disposeBag)
}
strikes.onNext("X")
strikes.onNext("X")
strikes.onNext("X")
strikes.onCompleted()
--- Example of: ignoreElements ---
You're out!

example(of: "elementAt") {

  // 1
  let strikes = PublishSubject<String>()

  let disposeBag = DisposeBag()

  //  2
  strikes
    .elementAt(2)
    .subscribe(onNext: { _ in
      print("You're out!")
    })
    .disposed(by: disposeBag)
}
strikes.onNext("X")
strikes.onNext("X")
strikes.onNext("X")
--- Example of: elementAt ---
You're out!

example(of: "filter") {
  let disposeBag = DisposeBag()

  // 1
  Observable.of(1, 2, 3, 4, 5, 6)
    // 2
    .filter { $0.isMultiple(of: 2) }
    // 3
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
--- Example of: filter ---
2
4
6

Skipping operators

When you want to skip a certain number of elements, use the skip operator. It lets you ignore the first n elements, where n is the number you pass as its parameter. This marble diagram shows skip is passed 2, so it ignores the first 2 elements.

example(of: "skip") {
  let disposeBag = DisposeBag()

  // 1
  Observable.of("A", "B", "C", "D", "E", "F")
    // 2
    .skip(3)
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
--- Example of: skip ---
D
E
F

example(of: "skipWhile") {
  let disposeBag = DisposeBag()

  // 1
  Observable.of(2, 2, 3, 4, 4)
    // 2
    .skipWhile { $0.isMultiple(of: 2) }
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
--- Example of: skipWhile ---
3
4
4

example(of: "skipUntil") {
  let disposeBag = DisposeBag()

  // 1
  let subject = PublishSubject<String>()
  let trigger = PublishSubject<String>()

  // 2
  subject
    .skipUntil(trigger)
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
subject.onNext("A")
subject.onNext("B")
trigger.onNext("X")
subject.onNext("C")
--- Example of: skipUntil ---
C

Taking operators

Taking is the opposite of skipping. When you want to take elements, RxSwift has you covered. The first taking operator you’ll learn about is take, which as this marble diagram depicts, will take the first of the number of elements you specified.

example(of: "take") {
  let disposeBag = DisposeBag()

  // 1
  Observable.of(1, 2, 3, 4, 5, 6)
    // 2
    .take(3)
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
--- Example of: take ---
1
2
3

example(of: "takeWhile") {
  let disposeBag = DisposeBag()

  // 1
  Observable.of(2, 2, 4, 4, 6, 6)
    // 2
    .enumerated()
    // 3
    .takeWhile { index, integer in
      // 4
      integer.isMultiple(of: 2) && index < 3
    }
    // 5
    .map(\.element)
    // 6
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
--- Example of: takeWhile ---
2
2
4

example(of: "takeUntil") {
  let disposeBag = DisposeBag()

  // 1
  Observable.of(1, 2, 3, 4, 5)
    // 2
    .takeUntil(.inclusive) { $0.isMultiple(of: 4) }
    .subscribe(onNext: {
      print($0)
    })
  .disposed(by: disposeBag)
}
--- Example of: takeUntil ---
1
2
3
4
--- Example of: takeUntil ---
1
2
3

example(of: "takeUntil trigger") {
  let disposeBag = DisposeBag()

  // 1
  let subject = PublishSubject<String>()
  let trigger = PublishSubject<String>()

  // 2
  subject
    .takeUntil(trigger)
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)

  // 3
  subject.onNext("1")
  subject.onNext("2")
}
--- Example of: takeUntil trigger ---
1
2
trigger.onNext("X")

subject.onNext("3")
_ = someObservable
    .takeUntil(self.rx.deallocated)
    .subscribe(onNext: {
        print($0)
    })

Distinct operators

The next couple of operators let you prevent duplicate contiguous items from getting through. As shown in this marble diagram, distinctUntilChanged only prevents duplicates that are right next to each other, so the second 1 gets through.

example(of: "distinctUntilChanged") {
  let disposeBag = DisposeBag()

  // 1
  Observable.of("A", "A", "B", "B", "A")
    // 2
    .distinctUntilChanged()
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
--- Example of: distinctUntilChanged ---
A
B
A

example(of: "distinctUntilChanged(_:)") {
  let disposeBag = DisposeBag()

  // 1
  let formatter = NumberFormatter()
  formatter.numberStyle = .spellOut

  // 2
  Observable<NSNumber>.of(10, 110, 20, 200, 210, 310)
    // 3
    .distinctUntilChanged { a, b in
      // 4
      guard
        let aWords = formatter
          .string(from: a)?
          .components(separatedBy: " "),
        let bWords = formatter
          .string(from: b)?
          .components(separatedBy: " ")
        else {
          return false
      }

      var containsMatch = false

      // 5
      for aWord in aWords where bWords.contains(aWord) {
        containsMatch = true
        break
      }

      return containsMatch
    }
    // 6
    .subscribe(onNext: {
      print($0)
    })
    .disposed(by: disposeBag)
}
--- Example of: distinctUntilChanged(_:) ---
10
20
200

Challenge

Challenges help solidify what you just learned. There are starter and finished versions of the challenge in the exercise files download.

Challenge: Create a phone number lookup

Run ./bootstrap.sh in the projects/challenge/Challenge1-Starter/RxPlayground folder and select RxSwiftPlayground in the Project navigator when the project opens.

let contacts = [
  "603-555-1212": "Florent",
  "212-555-1212": "Shai",
  "408-555-1212": "Marin",
  "617-555-1212": "Scott"
]
func phoneNumber(from inputs: [Int]) -> String {
  var phone = inputs.map(String.init).joined()

  phone.insert("-", at: phone.index(
    phone.startIndex,
    offsetBy: 3)
  )

  phone.insert("-", at: phone.index(
    phone.startIndex,
    offsetBy: 7)
  )

  return phone
}
let input = PublishSubject<Int>()
input.onNext(0)
input.onNext(603)

input.onNext(2)
input.onNext(1)

// Confirm that 7 results in "Contact not found",
// and then change to 2 and confirm that Shai is found
input.onNext(7)

"5551212".forEach {
  if let number = (Int("\($0)")) {
    input.onNext(number)
  }
}

input.onNext(9)
if let contact = contacts[phone] {
  print("Dialing \(contact) (\(phone))...")
} else {
  print("Contact not found")
}
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now