-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
Description
Ch.5 Filtering Operators
A. Getting Started
- 여기서 배울 것은 filtering operator로, 이 것을 통해
.next이벤트를 통해 받아오는 값을 선택적으로 취할 수 있다. - 기존 Swift에서
filter(_:)를 사용해봤다면 이해하기 쉬울 것이다.
B. Ignoring operators
1. .ignoreElements()
ignoreElements는.next이벤트를 무시.completed나.error같은 완료 이벤트는 허용.- Marble diagram 참고
let strikes = PublishSubject<String>()
let disposeBag = DisposeBag()
strikes
.ignoreElements()
.subscribe({ _ in
print("You're out!")
})
.disposed(by: disposeBag)
strikes.onNext("X")
strikes.onNext("X")
strikes.onNext("X")
strikes.onCompleted()
// "You're out!"2. .elementAt()
- Observable에서 방출된 n번째 요소만 처리하려는 경우
elementAt()을 사용. - 받고싶은 요소에 해당하는 index만을 방출하고 나머지는 무시.
let strikes = PublishSubject<String>()
let disposeBag = DisposeBag()
strikes
.elementAt(1)
.subscribe(onNext: { _ in
print("You're out!")
})
.disposed(by: disposeBag)
strikes.onNext("X")
strikes.onNext("X")
// "You're out!"
strikes.onNext("X")3. .filter
let disposeBag = DisposeBag()
Observable.of(1,2,3,4,5,6)
.filter({ (int) -> Bool in
int % 2 == 0
})
.subscribe(onNext: {
print($0)
// 2
// 4
// 6
})
.disposed(by: disposeBag)C. Skipping operators
1. .skip
let disposeBag = DisposeBag()
Observable.of("A", "B", "C", "D", "E", "F")
.skip(3)
.subscribe(onNext: {
print($0)
// D
// E
// F
})
.disposed(by: disposeBag)2. .skipWhile
-
.skipWhile은 어떤 요소를 skip하지 않을 때까지 skip하고 종료하는 연산자. -
skipwWhile은 skip할 로직을 구성하고 해당 로직이false되었을 때 방출한다.filter와 반대.
let disposeBag = DisposeBag()
Observable.of(2, 2, 3, 4, 4)
.skipWhile({ (int) -> Bool in
// 홀수인 요소가 나올 때까지 skip
int % 2 == 0
})
.subscribe(onNext: {
print($0)
// 3
// 4
// 4
})
.disposed(by: disposeBag)3. .skipUntil
-
다른 observable에 기반한 요소들을 다이나믹하게 필터링
-
skipUntil은 다른 트리거 observable이 시동하여.next이벤트를 방출하기 전까지는 기존 observable에서 방출하는 이벤트들을 skip.
let disposeBag = DisposeBag()
let subject = PublishSubject<String>()
let trigger = PublishSubject<String>()
subject
.skipUntil(trigger)
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
subject.onNext("A")
subject.onNext("B")
trigger.onNext("X")
subject.onNext("C")
// CD. Taking operators
1. take
let disposeBag = DisposeBag()
Observable.of(1,2,3,4,5,6)
.take(3)
.subscribe(onNext: {
print($0)
// 1
// 2
// 3
})
.disposed(by: disposeBag)2. takeWhile
3. enumerated
- 방출된 요소의 index를 참고하고 싶은 경우. 이럴 때는
enumerated연산자를 통해 확인 가능. - 기존 Swift의
enumerated메소드와 유사하게, Observable에서 나오는 각 요소의 index와 값을 포함하는 튜플을 생성하여 반환.
let disposeBag = DisposeBag()
Observable.of(2,2,3,4,6,6)
.enumerated()
.takeWhile({ index, element in
element % 2 == 0 && index < 3
})
.map { $0.element }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)4. takeUntil
-
takeUntil은skipUntil처럼 작동한다. -
takeUntil은 다른 트리거 observable이 시동하여.next이벤트를 방출하기 전까지는 기존 observable에서 방출하는 이벤트들을 취한다.
let disposeBag = DisposeBag()
let subject = PublishSubject<String>()
let trigger = PublishSubject<String>()
subject
.takeUntil(trigger)
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
subject.onNext("1")
// 1
subject.onNext("2")
// 2
trigger.onNext("X")
subject.onNext("3")E. Distinct operators
1. .distinctUntilChanged
- 그림에서처럼
distinctUntilChanged는 연달아 같은 값이 이어질 때 중복된 값을 막아주는 역할을 한다. 2는 연달아 두 번 반복되었으므로 뒤에 나온2가 배출되지 않음.1은 중복이긴 하지만 연달아 반복된 것이 아니므로 그대로 배출.
let disposeBag = DisposeBag()
Observable.of("A", "A", "B", "B", "A")
.distinctUntilChanged()
.subscribe(onNext: {
print($0)
// A
// B
// A
})
.disposed(by: disposeBag)2. .distinctUntilChanged(_:)
-
distinctUntilChanged는 기본적으로 구현된 로직에 따라 같음을 확인. 그러나 커스텀한 비교로직을 구현하고 싶다면distinctUntilChanged(_:)를 사용.
-
그림은
value라 명명된 값을 서로 비교하여 중복되는 값을 제외하고 있다.
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
// 각 스펠링을 분리(ex. ["ten"] ["one", "hundred", "ten"]) 하여 동일하게 일치하는 지 비교하여 flag 변경
for aWord in aWords {
for bWord in bWords {
if aWord == bWord {
containsMatch = true
break
}
}
}
// flag true 시 중복되었다고 판단되여 방출 X, 다음 요소 비교 진행
// flag false 시 중복이 아니라고 판단되여 비교 대상 방출
// a, b, c를 비교해가면서 만약 b가 a와 중첩되는 부분이 있어 prevent 되면, 다음엔 b와 c를 비교하는 것이 아니라 a와 c를 비교
return containsMatch
})
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)F. 구독 공유(Share)하기
1. Subscriber의 독립된 elements sequence
- Observable은 매우 게으른 pull-driven 시퀀스
- Observable에 아무리 여러 연산자를 호출해도 subscribe() 가 호출되기 전까진 아무 동작도 하지 않음
- Observable에 Subscribe를 한 횟수만큼 Observable 시퀀스가 생성
- subscriber는 각자 독립된 elements sequence를 가진다!
- Operator들은 기본적으로
stateless- 각각의 Operator들은 이전의 동작과
독립적으로 동작한다.
- 각각의 Operator들은 이전의 동작과
var disposeBag = DisposeBag()
var start = 0
func getStartNumber() -> Int {
start += 1
return start
}
let numbers = Observable<Int>.create { observer in
let start = getStartNumber()
observer.onNext(start)
observer.onNext(start+1)
observer.onNext(start+2)
observer.onCompleted()
return Disposables.create()
}
numbers
.subscribe(
onNext: { el in
print("element [\(el)]")
// element [1]
// element [2]
// element [3]
},
onCompleted: {
print("-------------")
}
)
.disposed(by: disposeBag)
numbers
.subscribe(
onNext: { el in
print("element [\(el)]")
// 두번째 subscribe을 하면서 getStartNumber()가 다시 호출되고 start값이 1 증가한 상태로 시작
// element [2]
// element [3]
// element [4]
},
onCompleted: {
print("-------------")
}
)
.disposed(by: disposeBag)2. share
API를 한번 콜하고 그 콜한 결과를 여러 곳에서 구독하여 다르게 쓰고 싶을 때 주로 사용
share()
- 1개의 구독의 이벤트를 여러 Observer가 공유
- 새로운 subscriber가 observing하기 전 과거 elements를 어떻게 다룰지(publish)와 언제 공유할지(refCount)가 필요하다.
- 이거를 publish().refCount(), 합쳐서 share(replay: 1)로 사용
- share() 연산자를 사용하면 Subscribe()할때마다 새로운 Observable 시퀀스가 생성되지 않고, 하나의 시퀀스에서 방출되는 아이템을 공유해 사용 가능
- share()는 구독이 영향을 받기 전까지는 어떠한 값 방출도 내지 않는다.
share(replay:scope:)
- replay에 넣는 인자는 버퍼의 크기를 의미
- 마지막 몇개의 방출 값에 대한 버퍼를 가지며 새로운 관찰자가 구독했을 때 이를 제공
- scope에 넣는 인자는 버퍼의 생명주기에 관한 것
- .forever : Subscription이 0이 되더라도 버퍼가 유지. 그래서 새로운 Subscription은 Subscribe() 를 하면 마지막에 버퍼에 남아있던 replay개수 만큼의 값을 수신하게 됩니다.
- .whileConnected : 1개 이상의 Subscriber가 존재하는 동안만 버퍼가 유지 됩니다. Subscription이 0이 되면 버퍼가 비워지고 새로운 Subscription은 버퍼에 남아 있던 값이 없으므로 replay시 새 값을 요청해 수신하게 됩니다.
class ViewController: UIViewController {
@IBOutlet weak var requestMoreButton: UIButton!
@IBOutlet weak var remainCountLabel: UILabel!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bind()
}
private func bind() {
// 서버에 API를 요청해 결과를 반환하는 시퀀스라 가정
let networkRequestAPI = Observable.of(100).debug("networkRequestAPI")
let result = requestMoreButton.rx.tap
.flatMap { networkRequestAPI }
// result 시퀀스를 공유하도록 처리
// .share()
result
.map { $0 > 0 }
.bind(to: requestMoreButton.rx.isHidden)
.disposed(by: disposeBag)
// bind(to:)는subscribe()의 별칭(Alias)으로 Subscribe()를 호출한 것과 동일
result
.map { "Count:\($0)" }
.bind(to: remainCountLabel.rx.text)
.disposed(by: disposeBag)
}
}
// .share를 하지 않은 경우 debug 결과
// networkRequestAPI -> subscribed
// networkRequestAPI -> Event next(100)
// networkRequestAPI -> Event completed
// networkRequestAPI -> isDisposed
// networkRequestAPI -> subscribed
// networkRequestAPI -> Event next(100)
// networkRequestAPI -> Event completed
// networkRequestAPI -> isDisposed
// .share를 한 경우 debug 결과
// networkRequestAPI -> subscribed
// networkRequestAPI -> Event next(100)
// networkRequestAPI -> Event completed
// networkRequestAPI -> isDisposed





