- 오늘의 주제는 filtering operator입니다.
- 다들 Swift에서
filter(_:)
를 사용해보신 적이 있으시죠? - 말그대로 Subject에 필터를 껴주는 작업입니다.
- 우리가 원하는 event 값만 쏙쏙 골라서 emit 해줄 수 있습니다. (개꿀~)
public func example(of description: String,
action: () -> Void) {
print("\n— Example of:", description, "—")
action()
}
- 지난 10월에 버전 6.0.0이 릴리즈 되었습니다.
- 오늘 공부하는 부분 중에 변경된 부분이 있습니다. 6.0.0 버전이 아니신 분들은 제 코드에서 오류가 뜰 수 있습니다.
- 아래의 릴리즈 노트를 참고해주세요!
- 릴리즈 노트 보러가기
ignoreElements
는.next
이벤트는 무시합니다.completed
나.error
같은 정지 이벤트는 허용합니다.
example(of: "ignoreElements") {
// 1
let strikes = PublishSubject<String>()
let disposeBag = DisposeBag()
// 2
strikes
.ignoreElements()
.subscribe(
onNext: { event in
print(event)
},
onCompleted: {
print("Complete")
}
)
.disposed(by: disposeBag)
strikes.onNext("1")
strikes.onNext("2")
strikes.onNext("3")
strikes.onCompleted()
}
출력을 해보셨나요?
— Example of: ignoreElements —
Complete
이렇게 1,2,3 모두 무시되고 complete만 출력되는 것을 확인할 수 있습니다.
- 우리는 방출된 특정 n번째의 이벤트만 처리하고 싶을 때가 있을 수 있습니다.
- 이럴 때 사용하는 것이
elementAt()
입니다 - 문법이 바뀌었습니다.
elementAt()
->element(at: )
이렇게 써줘야 합니다. - 당연하게 우리에게 index 0은 1번째 방출되는 이벤트 ^-^...
example(of: "elementAt") {
// 1
let strikes = PublishSubject<String>()
let disposeBag = DisposeBag()
// 2
strikes
.element(at: 2)
.subscribe(
onNext: { event in
print(event)
},
onCompleted: {
print("Complete")
}
)
.disposed(by: disposeBag)
strikes.onNext("a")
strikes.onNext("b")
strikes.onNext("c")
}
우선 어떻게 나올지 생각해보고, 출력을 해봅시다
— Example of: elementAt —
c
Complete
생각했던대로 나오셨나요?
ignoreElements
와element(at: )
은 observable의 요소들을 필터링하여 방출합니다.filter
는 필터링 요구사항이 한 가지 이상일 때 사용할 수 있습니다.
example(of: "filter") {
let disposeBag = DisposeBag()
// 1
Observable.of(1,2,3,4,5,6)
// 2
.filter({ (int) -> Bool in
int % 2 == 0
})
// 3
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
- Int 값을 받는 observable 생성
- filter를 생성하여 true 값을 반환하는 요소만 내보낸다!
- subscribe를 사용하여 방출되는 값을 확인한다
— Example of: filter —
2
4
6
End
- 몇개의 요소를 skip 하고 싶을 때가 있을 수 있다고 하네요 .. ( 왜? 언제? 모름 암튼 )
skip
연산자는 첫 번째 요소부터 n개의 요소를 skip하게 해줍니다
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)
}
앞의 3개 A,B,C 를 제외하고 D,E,F 만 출력되는 것을 확인하실 수 있습니다
— Example of: skip —
D
E
F
skipwWhile
은 skip할 로직을 구성하고 해당 로직이false
되었을 때 방출합니다.- 문법이 바뀌었습니다.
skipWhile(_:)
->skip(while:)
이렇게 써줘야 합니다.
example(of: "skipWhile") {
let disposeBag = DisposeBag()
// 1
Observable.of(2, 2, 3, 4, 4)
// 2
.skip(while: {$0 % 2 == 0})
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
- 홀수인 요소가 나올 때까지 skip합니다. 홀수인 요소가 나오면 그 후에는 계속 방출합니다.
- 보험금 청구 앱을 개발한다고 가정해보면, 공제액이 충족될 때까지 보험금 지급을 거부하기 위해
skipWhile
을 사용할 수 있습니다.
— Example of: skipWhile —
3
4
4
skipUnitl
은 다른 observable이 시동할 때까지 현재 observable에서 방출하는 이벤트를 skip 합니다skipUnitl
은 다른 observable이.next
이벤트를 방출하기 전까지는 기존 observable에서 방출하는 이벤트들을 무시하는 것 !!
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)
// 3
subject.onNext("A")
subject.onNext("B")
// 4
trigger.onNext("X")
// 5
subject.onNext("C")
}
코드를 실행해봅시다 !
- 주석을 따라 확인해보면,
- a)
subject
와trigger
라는 PublishSubject를 만든다. - b)
subject
를 구독하는데 그 전에.skipUnitl
을 통해trigger
를 추가한다. - c)
subject
에.onNext()
로A
,B
추가한다. - d)
trigger
에.onNext()
로X
를 추가한다. - e)
subject
에 새로운 이벤트C
를 추가한다. 그제서야C
가 방출되는 것을 콘솔에서 확인할 수 있다. 왜냐하면 그 전까지는.skipUnitl
이 막고 있었기 때문이다.
— Example of: skipUntil —
C
- Taking은 skipping의 반대 개념입니다.
- RxSwift에서 어떤 요소를 취하고 싶을 때 사용할 수 있는 연산자는
take
- 그림을 보면
take()
를 통해, 처음 2개의 값을 취한 것을 알 수 있습니다~~~
```swift
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
느낌이 오시죠 ?
takeWhile
은skipWhile
처럼 작동합니다.- 그림과 같이
takeWhile
구문 내에 설정한 로직에서true
에 해당하는 값을 방출하게 됩니다. (skip과 반대) - 문법이 바뀌었습니다.
takeWhile(_:)
->take(while:)
이렇게 써줘야 합니다.
- 방출된 요소의 index를 참고하고 싶은 경우가 있을 것입니다. 이럴 때는
enumerated
연산자를 확인할 수 있습니다! - 기존 Swift의
enumerated
메소드와 유사하게, Observable에서 나오는 각 요소의 index와 값을 포함하는 튜플을 생성하게 됩니다.
example(of: "takeWhile") {
let disposeBag = DisposeBag()
// 1
Observable.of(2, 4, 7, 8, 2, 5, 4, 4, 6, 6)
// 2
.enumerated()
// 3
.take(while: { index, value in
// 4
value % 2 == 0 && index < 3
})
// 4
.map { $0.element }
// 5
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
— Example of: takeWhile —
2
4
- 2, 4 까지는 설정한 로직이 true이지만 7은 false 입니다.
- 그 이후의 값들은 방출하지 않습니다.
skipUntil
이 있으니takeUntil
도 있겠죠 ?- 문법이 바뀌었습니다.
takeUntil(_:)
->take(until:)
이렇게 써줘야 합니다. - 그림과 같이, trigger가 되는 Observable이 구독되기 전까지의 이벤트값만 받는 것입니다. (skip과 반대라고 생각하면 됨)
example(of: "takeUntil") {
let disposeBag = DisposeBag()
// 1
let subject = PublishSubject<String>()
let trigger = PublishSubject<String>()
// 2
subject
.take(until:trigger)
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
// 3
subject.onNext("1")
subject.onNext("2")
trigger.onNext("X")
subject.onNext("3")
}
skip과 유사하니 빠르게 지나가겠습니다 - 슝
— Example of: takeUntil —
1
2
-
이 책 마지막에서 배울RxCocoa 라이브러리의 API를 사용하면 dispose bag에 dispose를 추가하는 방식 대신takeUntil
을 통해 구독을 dispose 할 수 있다. 아래의 코드를 살펴보자.someObservable .take(until:self.rx.deallocated) .subscribe(onNext: { print($0) })
-
이전의 코드에서는
takeUntil
의trigger
로 인해서subject
의 값을 취하는 것을 멈췄었다. -
여기서는 그 trigger 역할을
self의 할당해제
가 맡게 된다. 보통self
는 아마 뷰컨트롤러나 뷰모델이 될것이다.
- 여기서 배울 것은 중복해서 이어지는 값을 막아주는 연산자입니다.
- 그림에서처럼
distinctUntilChanged
는 연달아 같은 값이 이어질 때 중복된 값을 막아주는 역할을 합니다. 2
는 연달아 두 번 반복되었으므로 뒤에 나온2
가 배출되지 않음!1
은 중복이긴 하지만 연달아 반복된 것이 아니므로 그대로 배출됩니다.
example(of: "distincUntilChanged") {
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
너무 쉽죠? ㅎ-ㅎ
distinctUntilChanged
는 기본적으로 구현된 로직에 따라 같음을 확인합니다. 그러나 커스텀한 비교로직을 구현하고 싶다면distinctUntilChanged(_:)
를 사용할 수 있습니다.- 개인적으로 이게 좀 멋드러진다는 생각이 드네요 ㅎ-ㅎ ( 역시 커스텀이 멋지다 )
example(of: "distinctUntilChanged(_:)") {
let disposeBag = DisposeBag()
// 1
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
// 이 부분은 제가 아래 코드를 이해하기 위해서 실험해본 부분입니다... rx와는 상관 없음
let exampleNum :NSNumber = 110
print("example : \(formatter.string(from: exampleNum)!.components(separatedBy: " "))")
// 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 {
for bWord in bWords {
if aWord == bWord {
containsMatch = true
break
}
}
}
return containsMatch
}
// 4
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
}
— Example of: distinctUntilChanged(_:) —
10
20
200
why ?!?!
10 : ten
110 : one, hundred, ten -> ten 겹침
20 : twenty
200 : two , hundred
210 : two, hundred, ten -> two, hundred 겹침
310 : three, hundred, ten -> hundred, ten 겹침
- 10 : ten
- 110 : one, hundred, ten -> ten 겹침
- 20 : twenty
- 200 : two , hundred
- 210 : two, hundred, ten -> two, hundred 겹침
- 310 : three, hundred, ten -> hundred, ten 겹침
- 주석을 따라 확인해보자.
-
- 각각의 번호를 배출해내는
NumberFormatter()
를 만들어낸다.
- 각각의 번호를 배출해내는
-
NSNumbers
Observable을 만든다. 이렇게 하면formatter
를 사용할 때 Int를 변환할 필요가 없다.
-
distinctUntilChanged(_:)
는 각각의 seuquence 쌍을 받는 클로저다.
-
guard
문을 통해 값들의 구성요소를 빈 칸 구분하여 조건부로 바인딩하고 그렇지 않으면false
를 반환한다.
-
- 중첩
for-in
반복문을 통해서 각 쌍의 단어를 반복하고, 검사결과를 반환하여, 두 요소가 동일한 단어를 포함하는지 확인한다.
- 중첩
-
- 구독하고 출력한다.
- 결과는, 다른 요소를 포함하는 요소는 제외된 결과만 출력된다.
10 20 200
- a, b, c를 비교해가면서 만약 b가 a와 중첩되는 부분이 있어 prevent 되면, 다음엔 b와 c를 비교하는 것이 아니라 a와 b를 비교하게 됩니다. << 이해가 안감 토론 요망
-
끝!