[RxSwift] #7 Filtering Operators / Throttle & Debounce
raywenderlich RxSwift Ch5~6
1. Ignoring Operators
Swift 의 경우는 최신 버젼에서는 문법에 맞게, elementAt -> element(at: ), takeWhile -> take(while: ) 이런식으로 바뀌어 있었다.
1. .ignoreElements()
- .next 무시
- .completed, .error 는 허용
2. .element(at: index)
- 해당 index에 있는 요소만 방출
- index는 0부터 시작.
3. .filter( ((Any) -> Bool) )
- Any 에는 앞의 Observable 에서 next 로 오는 값이 들어간다.
- 안에 들어가는 함수(클로저) 의 return 값이 true 인 것만 남는다.
2. Skipping Operators
1. .skip(count)
- 처음부터 뛰어넘을 개수 count 를 입력해서 뛰어넘는다.
2. .skip(while: ((Any) -> Bool) )
- 안에 들어가는 함수(클로저) 의 return 값이 true 인 것을 건너뛴다.
- 그럼 filter 랑 반대이기만 하고 똑같은데 왜 씀?
- 조건에 맞지 않는 하나를 보내고 나면 그 뒤에는 skip 하지 않음.
Observable.of(2, 2, 3, 4, 4)
.skip(while: {
// 이 경우에는 조건에 맞지 않는 3부터 출력된다.
$0 % 2 == 0
})
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
// 3 4 4
3. .skip(until: Observable)
- 다른 observable 이 .next 를 emit 하기 전까지는 다 skip 됨.
3. Taking Operators
<-> Skipping
+) Observable 에도 .enumerated() 를 사용할 수 있다.
1. take(count)
- 처음부터 count 만큼 방출.
2. take(while: ((Any) -> Bool) )
- 인자로 받은 함수의 반환값이 true 인 것만 방출.
- skipWhile 이 skip 하다가 조건에 안맞는게 나왔을 때부터 방출하던 것과 반대로
- take 하다가 조건에 안맞는게 나왔을 때부터 방출 안함.
3. take(until: ((Any) -> Bool))
- 다른 Observable 이 .next 를 emit 하기 전까지만 방출
4. Distinct Operators
중복해서 이어지는 값을 없애준다.
1. .distinctUntilChanged()
- 바로 이어지는 값의 중복을 없애준다.
- 1 1 2 2 1 을 1 2 1 로 만들어준다.
2. .distinctUntilChanged(_: ((Any) -> Void) )
- distinctUntilChanged 와 기본 로직은 같다 ( 바로 이어지는 값의 중복만 없애줌 )
- 하지만, custom 가능
distinctUntilChanged {
$0.lastName == $1.lastName
}
5. Refactoring
#6에서 진행했던 프로젝트를 Filtering Object 를 이용해서 기능을 추가하고 리팩토링 해보았다. 역시 따라가면서 기억에 남는 것들 위주로 포스팅해보려고 한다.
1. share()
Observable 은 구독될 때마다 create 를 호출하기 때문에, 같은 Observable 을 구독해도 다른 결과가 나올 수 있다. 게다가 같은 데이터를 받을 건데 Observable 을 구독자 별로 다르게 유지하는 것은 비효율적이다.
그래서 하나의 구독을 공유하기 위해서 share() 를 이용한다.
let newImage = photosViewController.selectedPhotos.share() // newImage 를 구독하여 각각 처리를 해주면 된다.
- create : share() 를 이용하면 구독자 수가 1이 될 때 구독을 생성하고, 이미 생성된 구독을 다른 구독자와 공유한다.
- dispose : 공유된 sequence 에 대한 모든 구독이 dispose 되면 공유된 sequence 도 dispose 한다.
- method
share()
: 구독이 영향을 받기 전까지는 어떤 값도 방출하지 않는다.share(replay: , scope: )
는 마지막 몇 개의 방출값에 대한 버퍼를 제공한다. (새로운 구독자에게 마지막 몇 개를 바출한다.)sharing
의 경우 completed 되지 않는 Observable 에 사용한다.
2. caching
Observable 은 현재 값이나 히스토리를 제공하지 않기 때문에 특정 요소를 확인하기 위해서는 각 요소 자체가 스스로를 추적하게 해야한다.
// imageCache 배열로, 이미 있는 이미지인지 확인하는 과정을 거쳤다. .filter { [weak self] newImage in // 보통 assetId 같은 것을 이용하지만, 예제라서 이미지 크기를 사용하여 비교. let len = newImage.pngData()?.count ?? 0 guard self?.imageCache.contains(len) == false else { return false } self?.imageCache.append(len) return true }
3. 코드 리팩토링에서 중요한 것.
10줄짜리 코드를 3줄로 줄이는 것만이 좋은 리팩토링 방식이 아니다!! 예제에서는 PHPhotoLibrary 에 사진 접근 권한 관련 코드를 쓸 때, 이런 방식으로 코드를 짰다.
// 승인이 되어 있으면 true
// 안되어 있으면 false 보내고 다시 권한 요청해서 결과값
extension PHPhotoLibrary {
static var authorized: Observable<Bool> {
return Observable.create() { observer in
if authorizationStatus() == .authorized {
observer.onNext(true)
observer.onCompleted()
} else {
observer.onNext(false)
requestAuthorization { newStatus in
observer.onNext(newStatus == .authorized)
observer.onCompleted()
}
}
return Disposables.create()
}
}
}
- 꼭 쓸 필요가 없어도 코드를 한두줄 추가해서 의미가 명확해지고 읽기 편해진다면 그렇게 하자!
// 어차피 앱을 맨 처음 켜고 첫번째 값은 false 임. // 권한 승인을 해줘서 값이 true 일 때 let authorized = PHPhotoLibrary.authorized.share() authorized .skipWhile { $0 == false } .take(1) // 쓸 필요가 없지만 의미가 명확해진다. .subscribe(onNext: { [weak self] _ in self?.photos = PhotosViewController.loadPhotos() DispatchQueue.main.async { self?.collectionView?.reloadData() } }) .disposed(by: bag)
filter{$0 == false}
== filter{!$0}
== filter{!}
물론 줄여서 많이 사용하는 것은 줄여 써도 괜찮음.
- Observable 에서 넘어오는 데이터의 sequence 가
절대불변
할 로직이 아니라면 로직을 생략하지 말자.// 권한 승인을 거절해서 값이 false 일 때 // 어차피 앱 처음 켠 false, 권한 승인 거절 false 로 2개의 값이 오지만 skip, takeLast를 둘 다 한 것 처럼 로직을 생략하지 말자. // _ = authorized .skip(1) .takeLast(1) .filter { $0 == false } .subscribe(onNext: { [weak self] _ in guard let errorMessage = self?.errorMessage else { return } DispatchQueue.main.async(execute: errorMessage) })
3. throttle
너무 많은 사용자 입력값을 걸러주는 코드로
여러번 들어오는 동일 이벤트를 한번만 전달한다.// 0.5 초간 들어오는 입력을 모아서 반응한다. // 즉각적으로 반응하지 않게! .throttle(0.5, scheduler: MainScheduler.instance)
우리가 만든 예제에서는 사용자가 사진을 선택할 때마다 새로운 콜라주를 만드는게 자원 낭비이므로, 0.5초 내에 선택되는 여러개의 사진에 대해서는 일일이 콜라주를 만들지 않게 throttle 을 걸었다.
이 외에도 textField 입력할 때나
모달이 뜨기 전에 버튼을 연타했을 때 모달이 여러개 뜨지 않도록 방지 한다던가
드래그 할 때 드래그 끝나는 위치만 알고 싶을 때 사용할 수 있다.
4. Debounce
debounce 는 예제에는 없었지만 throttle 과 항상 묶여서 다니는 개념인 것 같아서 함께 찾아봤다.
debounce 는 일정 시간동안 발생하는 이벤트를 모아서!! 한번에 전달한다.
debounce(for: .seconds(2), lastest: true, scheduler: MainScheduler.instance)
지정된 시간 2초 안에서 마지막에 들어온 것을 emit하는 옵션인 lastest 를 설정할 수 있다. 기본값이 true! (throttle 도 설정할 수 있다.)
latest: false
이면 첫번째 값만을 emit 하고 2초 동안 들어오는 값은 다 무시한 뒤, 2초가 지나고 나서 언젠가 또 들어오는 첫번째 값만 emit 하게 된다.
느낀점
filtering operator 가 그렇게 어렵진 않다고 생각했는데, takeWhile, skipWhile 이 생각보다 헷갈렸다. 그리고 여기 있는게 다가 아니라 또 아주 많은.. operator 들이 있다고 생각하니까 적시적기에 맞는 것을 찾아 쓰는 것도 어려울 것 같다는 생각이 들었다.
PHPhotoLibrary.autorized
를 Observable 을 이용해서 다루는게 신기했다. 여태 함수 return 값으로만 줬었는데, computed property 를 사용해서도 Observable 을 사용할 수 있다는게.. 배울 부분이 엄청 많은 것 같다.
출처 - https://github.com/fimuxd/RxSwift/tree/master/Lectures
이미지 출처 - https://reactivex.io/
Author And Source
이 문제에 관하여([RxSwift] #7 Filtering Operators / Throttle & Debounce), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@ddosang/RxSwift-7-Filtering-Operators저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)