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 krvuwpnoc text.

Heads up... You’re accessing parts of this content for free, with some sections shown as mlvofpvaw 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.

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

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

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

Unlock now

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!

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

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

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

Unlock now

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!

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

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

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

Unlock now

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)
}

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

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

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

Unlock now
--- 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

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

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

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

Unlock now

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")

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

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

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

Unlock now
--- 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)
}

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

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

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

Unlock now
--- 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

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

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

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

Unlock now
--- 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)
    })

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

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

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

Unlock now

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)
}

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

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

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

Unlock now
--- 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.

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

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

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

Unlock now
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")
}

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

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

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

Unlock now
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.
© 2025 Kodeco Inc.

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

Unlock now