apple/RxSwift, ReactorKit

[week4] Filtering Observables

lgvv 2021. 7. 10. 12:19

[week4] Filtering Observables

 

이번 실습코드는 

https://github.com/lgvv/MyRxSwift

 

lgvv/MyRxSwift

나의 RxSwift 공부 기록장. Contribute to lgvv/MyRxSwift development by creating an account on GitHub.

github.com

 

커리큘럼은 이번에는 공식문서를 기반으로 함.

 

Filtering Observabls


이 순서대로 한번 알아보도록 할건데, 여기서 약간의 메소드 사용법이 달라진 것도 있으니 유의하면서 보도록 하자.

 

 

 (목차)

1. 공식문서 카테고리에 나온 것들을 정리해보자.

2. 그 외에 것들에 대해서 알아보자.

 - enumerated

 - throttle

 - single

 

 

✅ 1. 공식문서 카테고리에 나온 것들을 정리
1️⃣ Debounce

 - 어떤 이벤트가 발생하면 일정한 딜레이를 갖는데, 그 사이에 또 다른 이벤트가 들어오면 다시 딜레이가 초기화되고, 딜레이 안에 어떤 값도 들어오지 않으면 그 마지막 이벤트만을 방출한다.

Debounce

    @IBOutlet weak var debounceLabel: UILabel!
    @IBOutlet weak var debounceBtn: UIButton!
    @IBOutlet weak var debounClickLabel: UILabel!
    
    var debounceCount = 0
    var debounceClickCount = 0
    
    // viewDidAppear
    print(" ===== debounce ===== ")
    debounceBtn.rx.tap.asDriver()
            .debounce(.seconds(2))
            .drive(onNext: { (_) in
                self.debounceCount += 1
                self.debounceLabel.text = "\(self.debounceCount)"
            }).disposed(by: disposeBag)
            
    // class filtering
    @IBAction func DebounceBtn(_ sender: Any) {
        self.debounceClickCount += 1
        self.debounClickLabel.text = "\(self.debounceClickCount)"
        print("\(self.debounceClickCount)")
    }

테스트를 위해서 이렇게 작성하였는데, 이렇게 작성할 경우 우리가 클릭을 아무리 많이해도 debounce 값은 변경되지 않는다는 것을 확인할 수 있다.

현재 단계에서 asDriver같은게 이해하는데 어려울 수 있으나, 일단은 로직만 알고 넘어가고 뒤의 포스팅에서 자세하게 설명

 

2️⃣ DistinctUntilChanged

 - 연속으로 중복된 아이템을 지워준다.

DistinctUntilChanged

공식문서에서 distinct의 설명을 보면 연속으로 중복된 것을 지워주는게 아닌 파이썬의 set처럼 중복되는 모든 것이라고 하는데 막상 메소드를 찾아보니,, 나타나지 않는다. 아마도 RxSwift에서는 안되고 다른 언어에서 지원해주는 것이 아닐까 싶다.

그래서 DistinctUntilChanged로 알아보았는데, 코드를 한번 보자.

        print(" ===== distinctUntilChanged ===== ")
        Observable.of("A","A","B","B","A","C","c")
            .distinctUntilChanged()
            .subscribe(onNext : {
                print($0)
            }).disposed(by: disposeBag)
            
// 결과값
A
B
A
C
c

대소문자도 구분이 된다.

🔸그런데 얘는 커스텀해서 사용할 수도 있다는 장점이 있다.

이때는 잘 보면 (:_) 이렇게 표현된 것을 볼 수 있는데, 코드를 읽으면 쉽게 이해할 수 있으니 천천히 보길 바란다.

// viewDidAppear
        print(" ===== distinctUntilChanged(:_) ")
        customDistinct()

// extension filtering

    func customDistinct() {
        let formatter = NumberFormatter()
        formatter.numberStyle = .spellOut // 애플 공식 문서에 따르면 숫자의 포맷을 맞춤 en으로 맞춰진다.
        /*
         ex) 123 : one, hundred, twenty-three
         10 : ten
         110 : one, hundred, ten -> ten 겹침
         20 : twenty
         200 : two , hundred
         210 : two, hundred, ten -> two, hundred 겹침
         310 : three, hundred, ten -> hundred, ten 겹침
         */
        Observable<NSNumber>.of(10,110,20,200,210,310)
            .distinctUntilChanged {a, b in
                //각 숫자를 [String] 으로 쪼개서 넣기
                guard let aWords = formatter.string(from: a)?.components(separatedBy: " "),
                        let bWords = formatter.string(from: b)?.components(separatedBy: " ")
                else { return false }
                
                var containsMatch = false
                   
                //배열을 돌아가면서 a가 b 에 포함되는지 체크
                for aWord in aWords where bWords.contains(aWord) {
                    containsMatch = true
                    break
                }
                return containsMatch
                // return true가 반환되면 종료되서 subscribe쪽으로 들어가질 않게 돼.
            }.subscribe(onNext: {print($0)})
            .disposed(by: disposeBag)
       }
       
       
// 결과값
10
20
200

여기서는 띄어쓰기를 기준으로 분류한 것을 배열로 넣었다가, 배열의 원소 중 중복되는 것이 있다면, 중복처리하는 메소드이다.

 

3️⃣ elementAt

 - 인덱스에 맞는 이벤트만 꼭 집어서 방출해준다. , 예를 들면 우리가 특정하게 n번째에 발생한 이벤트만 처리하고 싶을 때

❗️문법이 바뀌었습니다. elementAt() -> element(at: ) 이렇게 써줘야 합니다. SOPT내용에 이렇게 작성되어 있었으나, 실제로 작성해본 결과 문법이 바뀌지 않았음을 확인하였음.

 

elementAt

사용법은 이렇게 되고 코드를 한번 살펴볼까?

        let strikes = PublishSubject<String>()
        print(" ===== elementAt ===== ")
        Observable.of(0,1,2,3,4,5,6,7,8,9)
            .elementAt(3)
            .subscribe(onNext : {
                print($0)
            }).disposed(by: disposeBag)
        
        strikes.elementAt(2)
            .subscribe(onNext : { event in
            print(event)
            }, onCompleted: {
                print("Complete")
            }).disposed(by: disposeBag)
        strikes.onNext("A")
        strikes.onNext("B")
        strikes.onNext("C")
        
// 결과값
3
C
Complete

코드를 보면 쉽게 이해할 수 있다!

 

4️⃣ Filter

 - 안의 내용을 만족한 것들만 방출한다.

Filter

        print(" ===== Filter ===== ")
        Observable.of(1,2,3,4,5,6)
            .filter({ (int) -> Bool in
                int % 2 == 0
            })
            .subscribe(onNext : {
                print($0)
            }).disposed(by: disposeBag)
            
// 결과값
2
4
6

코드를 보면 쉽게 이해할 수 있다.

물론 Subjects에도 붙일 수 있으니까 응용은 스스로 해보길 바란다. 그렇게 어렵지 않아요~!

 

5️⃣ First

 - First는 스트림의 첫번째 부분만 내보내준다.

First

First는 처음꺼만 처리하므로 onNext가 들어갈 경우 에러가 난다.

First에서는 onNext가 필요 없다

        print(" ===== First ===== ")
        Observable.of(1,2,3,4,5,6)
            .first()
            .subscribe({
                print("observable - first success \($0)")
            }).disposed(by: disposeBag)
        
        strikes.first().subscribe(onSuccess: {
            print("strike - first sucess \($0)")
        }, onError: nil).disposed(by: disposeBag)
        strikes.first().subscribe(onSuccess: {
            print("strike - second sucess \($0)")
        }, onError: nil).disposed(by: disposeBag)
        strikes.onNext("C")
        strikes.onNext("D")
        
// 결과값
observable - first success success(Optional(1))
strike - first sucess Optional("C")
strike - second sucess Optional("C")

결과값이 다음과 같이 나오는 것을 확인할 수 있어 그렇게 어렵지 않지? strike의 경우에는 onNext를 통해 값이 들어왔을때, success되는데, PublishSubject가 아닌 다른 것을 사용한다면 이전 값이 존재하면 그걸 읽고 바로 끝낼 수도 있음.

 

6️⃣ ignoreElements

 - 방출하진 않지만, 종료 알림은 하겠다. 즉, next는 무시하고 에러나 컴플릿의 스탑 이벤트만 처리하겠다는 의미

ignoreElements

        print(" ===== ignoreElements ===== ")
        strikes.ignoreElements()
            .subscribe {
                print("complete")
            } onError: { error in
                print("error -> \(error.localizedDescription)")
            }
        strikes.onNext("1")
        strikes.onNext("2")
        strikes.onNext("3")
        strikes.onCompleted()

// 결과값
complete

코드를 보면 쉽게 이해가 가능.

 

7️⃣ Last

 - Last는 마지막 아이템만 방출한다. 이것의 경우에는 last로 직접 구현되는게 아니라, 특정 함수를 구현하는 차단 함수로 구현하며, 이에 따라서 필터링 연산자에서 우리는 takelast(1)을 이용한다

last

        print(" ===== takelast ===== ")
        Observable.of(1,2,3,4,5)
            .takeLast(1)
            .subscribe(onNext : { print($0) })
            .disposed(by: disposeBag)

// 결과값 
5

 

8️⃣ Sample

 - 특정 기간 사이에 있는 아이템 중 가장 최근의 아이템만을 방출한다.

이게 무슨말인지 잘 이해 안가면 바로 코드를 보자

sample

 let data = PublishSubject<String>() // 선언부
 let trigger = PublishSubject<Void>() // 선언부
 
        print(" ===== smaple ===== ")

        data.sample(trigger)
            .subscribe{ print($0) }
            .disposed(by: disposeBag)
        
        trigger.onNext(())
        data.onNext("first")
        
        data.onNext("last")
        trigger.onNext(())
        
        trigger.onNext(())
        
        data.onCompleted()
        
        trigger.onNext(())
        
// 결과값
next(last)
completed

결과값을 보면 쉽게 이해가 가지? data 서브젝트가 아무리 들어와도 trigger이벤트가 발생할 때, trigger이벤트 사이의 값중에서 가장 나중의 data서브젝트에 있는 값을 방출한다.

 

9️⃣ skip & skipLast & skipWhile & 

 - skip : 앞에서부터 skip(value) value에 적힌 값의 갯수만큼 스킵한 후에 보여준다.

 - skipLast : 앞에서부터 n개를 스킵하는데, 스킵한 값이 다음 밸류가 들어오면 이게 맞게 보여진다.

 - skipWhile : 어떤 특정한 조건이 만족하기 전까지 모두 다 스킵

 - skipUntil : 특정 이벤트 발생 후 그 시점부터 방출함. 그전까지 모두 스킵

skip & skipLast
skipWhile & skipUntil

그럼 코드로도 확인해 볼까?

        let trigger = PublishSubject<Void>() // 선언부 
        let subject = PublishSubject<String>() // 선언부
        
        print(" ===== skip ===== ")
        Observable.of(1,2,3,4,5,6)
            .skip(2)
            .subscribe({ print($0) })
            .disposed(by: disposeBag)
        
        print(" ===== skipWhile ===== ")
        Observable.of(2,2,3,2,4,5,6)
            .skipWhile({$0 % 2 == 0})
            .subscribe(onNext : { print($0) })
            .disposed(by: disposeBag)
        
        print(" ===== skipUntil ===== ")
        subject
            .skipUntil(trigger)
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
                
        subject.onNext("A")
        subject.onNext("B")
        trigger.onNext(())
        subject.onNext("C")
        
// 결과값
 ===== skip ===== 
next(3)
next(4)
next(5)
next(6)
completed
 ===== skipWhile ===== 
3
2
4
5
6
 ===== skipUntil ===== 
C

코드를 보면 정말 쉽게 이해 가능하지? skipLast의 경우에는 메소드가 찾아지지가 않아서 이것도 rxswift에서는 없는것으로 생각 돼.

 

🔟 Take & TakeLast & TakeWhile & TakeUntil

 - Take : 앞에서부터 정해진 숫자만큼만 방출해

 - TakeLast : 뒤에서부터 정해진 숫자만큼만 방출해

 - TakeWhile : 특정 조건이 만족되기 전까지만 방출해

 - TakeUntil : 특정 이벤트가 발생하기 전까지만 방출해

 

Take & TakeLast
TakeWhile &  TakeUntil

 

그럼 코드로 한번 봐볼까?

        print(" ===== take ===== ")
        Observable.of(1,2,3,4,5,6)
            .take(3)
            .subscribe(onNext : { print($0) })
            .disposed(by: disposeBag)
        
        print(" ===== takelast ===== ")
        Observable.of(1,2,3,4,5,6)
            .takeLast(3)
            .subscribe(onNext : { print($0) })
            .disposed(by: disposeBag)
        
        print(" ===== takewhile ===== ")
        Observable.of(2,2,3,2,4,5,6)
            .takeWhile({ $0 % 2 == 0})
            .subscribe(onNext : { print($0) })
            .disposed(by: disposeBag)
        
        print(" ===== takeuntil ===== ")
        subject
            .takeUntil(trigger)
            .subscribe(onNext: { print("subscribe value \($0)") })
            .disposed(by: disposeBag)
                
        subject.onNext("1")
        subject.onNext("2")
        trigger.onNext(())
        subject.onNext("C")
        
// 결과값
 ===== take ===== 
1
2
3
 ===== takelast ===== 
4
5
6
 ===== takewhile ===== 
2
2
 ===== takeuntil ===== 
1
subscribe value 1
2
subscribe value 2
C

결과값을 보면 쉽게 이해가 가지? 

⚠️ takeUntile 쪽에 subscribe으로 들어가야 log에 출력되어야 한다고 생각하는데, 도대체 1,2,C는 왜 찍히는걸까? 누가 알면 알려줘요 😂

 

 

✅ 2. 그 외에 것들에 대해서 알아보자.

 1️⃣ enumerated 

  - 방출된 요소의 index를 참고하고 싶은 경우가 있을 것입니다. 이럴 때는 enumerated 연산자를 확인할 수 있습니다.

  - 기존 Swift의 enumerated 메소드와 유사하게, Observable에서 나오는 각 요소의 index와 값을 포함하는 튜플을 생성하게 됩니다.

        print(" ===== enumerated ===== ")
        Observable.of(2, 4, 7, 8, 2, 5, 4, 4, 6, 6)
            .enumerated()
            .takeWhile({ index, value in
                value % 2 == 0 && index < 4
            })
            .map { $0.element }
            .subscribe(onNext: {
                print($0)
            })
            .disposed(by: disposeBag)

// 결과값
2
4

// map을 주석처리 하였을 때의 결과값
(index: 0, element: 2)
(index: 1, element: 4)

코드보면 쉽게 이해가 간다! map을 통해 원소를 매핑해주는 작업이 필요해!

 

2️⃣ throttle

 - 입력 - > 바로 입력 -> 대기

 - 주로 버튼 중복 입력 방지에 사용

 - Debounce와 비슷하나, Debounce는 마지막 이벤트를 기준으로 딜레이 후 보여진다면, 얘는 클릭 이후 n초간 딜레이를 갖는다.

 

        print(" ===== throttle ===== ")
        ThrottleBtn.rx.tap.asDriver()
            .throttle(.seconds(3))
            .drive(onNext: { (_) in
                self.ThrottleCount += 1
                self.ThrottleLabel.text = "\(self.ThrottleCount)"
            }).disposed(by: disposeBag)

 

3️⃣ single

 - siwft sequence의 first와 같으나 아래 보이다 시피 매개변수를 넣을 수 있따.

single은 매개변수를 넣을 수 있다.

        print(" ===== single ===== ")
        Observable.of("📱", "⌚️", "💻", "🖥")
            .single()
            .subscribe { print($0) }
            .disposed(by: disposeBag)
// 결과값
next(📱)

 

 

 

 

 

 

'apple > RxSwift, ReactorKit' 카테고리의 다른 글

[week6] Combining Observables  (0) 2021.07.11
[week5] 🌟Transforming Observables  (0) 2021.07.10
[week3] Subjects  (0) 2021.07.10
🐉 RxSwift(Operators) Creating Observables  (0) 2021.07.09
[week2] 👀 Observserbles  (0) 2021.07.08