RxSwift
5. Filtering Operators
kangwook
2022. 12. 19. 15:40
- Operator는 Rx의 빌딩 블록으로, Observable 에서 발생하는 이벤트를 변환, 처리 및 반응하는데 사용할 수 있다.
- +, -, / 와 같은 단순한 산술 연산자를 결합하여 복잡한 수식을 만드는 것처럼 Rx의 간단한 연산자를 연결하고 함께 구성하여 복잡한 앱의 논리를 표현할 수 있다.
Ignoring Operators
- ignoreElements는 .next 이벤트 아이템을 무시
- 그러나 .completed 또는 .error 이벤트와 같은 중지 이벤트를 허용
example(of: "ignoreElements") {
let strikes = PublishSubject<String>() // Create strikes subject
let disposeBag = DisposeBag()
strikes // Subscribe to all strikes events, but ignore all.
.ignoreElements()
.subscribe { _ in
print("You're out!")
}
.disposed(by: disposeBag)
strikes.onNext("X")
strikes.onNext("X")
strikes.onNext("X")
strikes.onCompleted()
}
- ignoreElement가 실제로 Completable을 반환
- 이 경우 완료 또는 오류 이벤트만 발생할 수 있다.
- Observable에 의해 방출된 n번째 아이템만 처리하려는 경우가 있을 수 있다.
- 이를 위해 수신하려는 아이템의 인덱스를 사용하는 elementAt을 사용할 수 있다. → 현재는 elementAt은 deprecated. element(at: )으로 변경
- 나머지는 모두 무시
example(of: "elementAt") {
let strikes = PublishSubject<String>()
let disposeBag = DisposeBag()
strikes
.element(at: 2)
.subscribe(onNext: { _ in
print("You're out!")
})
.disposed(by: disposeBag)
strikes.onNext("X")
strikes.onNext("X")
strikes.onNext("X")
}
- ignoreElements 및 element(at: )은 Observable에서 내보낸 필터링 아이템
- filter는 true로 해석되는 아이템만 통과하도록 허용
example(of: "filter") {
let disposeBag = DisposeBag()
Observable.of(1, 2, 3, 4, 5, 6)
.filter { $0 % 2 == 0 }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
Skipping Operators
- 특정 수의 아이템을 건너뛰어야 할 수도 있다.
- skip 연산자를 사용하면 첫 번째부터 매개 변수로 전달한 숫자까지 무시할 수 있다.
example(of: "skip") {
let disposeBag = DisposeBag()
Observable.of("A", "B", "C", "D", "E", "F")
.skip(3)
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
- skipWhile을 사용하면 특정 조건을 만족하는 구간동안 건너뛸 수 있다.
- 그리고 만족하지 않는 그 이후부터는 모든 아이템을 방출한다.
- skipWhile을 사용하면 true를 반환하면 아이템을 건너뛰고, false를 반환하면 아이템을 방출할 수 있다. → filter와 반대
example(of: "skipWhile") {
let disposeBag = DisposeBag()
Observable.of(2, 2, 3, 4, 4)
.skip(while: { $0 % 2 == 0 }) // skipWhile은 deprecated. skipWhile이 skip(while: ) 형태로 변경
.subscribe {
print($0)
}
.disposed(by: disposeBag)
}
- 지금까지의 필터링은 일부 정적 조건을 기반
- 동적으로 필터링을 진행할 수도 있다.
- skipUntil은 다른 트리거 Observable이 방출될 때까지 아이템을 계속 건너뛴다.
- skipUntil은 트리거 Observable이 .next 이벤트를 방출할 때까지 방출된 아이템을 무시. 그런 다음 트리거 Observable이 방출된 시점부터 모든 것을 방출하기 시작
example(of: "skipUntil") {
let disposeBag = DisposeBag()
let subject = PublishSubject<String>()
let trigger = PublishSubject<String>()
subject
.skip(until: trigger) // skipUntil은 deprecated. skipUntil이 skip(until: ) 형태로 변경
.subscribe {
print($0)
}
.disposed(by: disposeBag)
subject.onNext("A")
subject.onNext("B")
trigger.onNext("X")
subject.onNext("C")
}
Taking Operators
- Taking은 Skip과 반대로 아이템을 가져올 수 있다.
- take 연산자를 사용하여 가져오는 방법이 있다.
example(of: "take") {
let disposeBag = DisposeBag()
Observable.of(1, 2, 3, 4, 5, 6)
.take(3)
.subscribe {
print($0)
}
.disposed(by: disposeBag)
}
- takeWhile은 skipWhile과 유사하게 동작
- 방출되는 아이템의 인덱스를 참조할 수도 있다.
- 이를 위해 Observable에서 방출된 각 아이템의 인덱스와 아이템을 포함하는 튜플을 생성하는 열거 연산자를 사용할 수 있다.
example(of: "takeWhile") {
let disposeBag = DisposeBag()
Observable.of(2, 2, 4, 4, 6, 6)
.enumerated() // enumerated() 를 통해 방출된 각 아이템의 인덱스와 값을 포함하는 튜플을 가져온다.
.take(while: { index, integer in // takeWhile은 deprecated. takeWhile은 take(while: ) 형태로 변경
integer % 2 == 0 && index < 3 // 방출할 아이템의 조건
})
.map { $0.element } // map을 사용하여 튜플에 접근하여 아이템을 가져온다.
.subscribe {
print($0)
}
.disposed(by: disposeBag)
}
- skipUntil과 마찬가지로 takeUntil 연산자도 있으며 트리거 Observable이 아이템을 방출할 때까지 아이템을 가져온다.
example(of: "takeUntil") {
let disposeBag = DisposeBag()
let subject = PublishSubject<String>()
let trigger = PublishSubject<String>()
subject
.take(until: trigger)
.subscribe {
print($0)
}
.disposed(by: disposeBag)
subject.onNext("1")
subject.onNext("2")
trigger.onNext("X")
subject.onNext("3")
}
// 여기서 takeUntil을 중지시키는 트리거는 ViewController 또는 ViewModel인 self의 할당해제
_ = someObservable
.take(until: self.rx.deallocated)
.subscribe {
print($0)
})
Distinct Operators
- distinctUntilChanged 를 사용하면 바로 옆에 있는 중복만 방지
example(of: "distinctUntilChanged") {
let disposeBag = DisposeBag()
Observable.of("A", "A", "B", "B", "A")
.distinctUntilChanged()
.subscribe {
print($0)
}
.disposed(by: disposeBag)
}
- 앞의 예에선 String 타입이 Equtable 프로토콜을 준수하는 타입이기 때문에 Equatable을 기반으로 동등성을 비교
- 그러나 외부에서 이름이 지정되지 않은 매개 변수를 비교하기 위해선 distinctUntinChanged(_ : )를 사용하여 비교할 수 있다.
example(of: "distinctUntilChanged(_ :)") {
let disposeBag = DisposeBag()
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
Observable<NSNumber>.of(10, 110, 20, 200, 210, 310)
.distinctUntilChanged { a, b in
guard let aWords = formatter.string(from: a).components(separatedBy: " "),
let bWords = formatter.string(from: b).components(separatedBy: " ") else { return false }
var containsMatch = false
for aWord in aWords where bWords.contains(aWord) { // 첫번째 배열의 모든 단어를 반복하고 두번째 배열에 포함되어 있는지 확인
containsMatch = true
break
}
return containsMatch
}
.subscribe {
print($0)
}
.disposed(by: disposeBag)
}