천원의 개발

RxSwift Operator 정리 본문

iOS&Swift🍎/RxSwift

RxSwift Operator 정리

천 원 2023. 9. 11. 17:06

안녕하세요. 천원입니다. 

RxSwift를 사용하면서 너무 데이터 바인딩의 목적으로만 사용하는 것 같아 반성하고자 다양한 오퍼레이터들을 정리 해보려고 합니다. 자주 사용하는 오퍼레이터들은 별표를 붙여두었습니다.

 

Transforming Observables

- map ☆ 

옵저버블의 각 항목을 변환하여 새로운 항목을 생성합니다.

Observable.of(1, 2, 3)
    .map {
        $0 * 2
    }
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag) 
    
    // 2, 4, 6 출력

 

- flatMap

 옵저버블의 항목을 다른 옵저버블로 변환하고 병합합니다.

Observable.of(1, 2, 3)
    .flatMap {
        Observable.of($0 * 2, $0 * 4)
    }
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)
    // 2, 4, 4, 8, 6, 12 출력

map은 주로 각 항목을 일대일 매핑을 할 때 사용되고, flatMap은 각 항목을 다른 옵저버블로 매핑하고 비동기로 처리한 작업을 병합하기 때문에 비동기 작업에 적합합니다.

 

- scan

시퀀스의 각 항목에 대해 누적된 결과를 생성합니다.

Observable.of(1, 2, 3, 4, 5)
    .scan(0) { accumulated, value in
        return accumulated + value
    }
    .subscribe(onNext: { value in
        print(value)
    })
    .disposed(by: disposeBag)
    // 1, 3, 6, 10, 15 출력

0을 초기값으로 반환하는 값들을 누적해서 처리합니다.

 

 

Filtering Observables

- filter

주어진 조건을 만족하는 옵저버블 한목만 유지합니다.

Observable.of(1, 2, 3, 4)
    .filter {
        $0 % 2 == 0
    }
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)
    // 2, 4 출력

 

- distinctUntilChanged ☆

연속적으로 중복된 항목을 필터링합니다.

Observable.of(1, 1, 1, 2, 3)
    .distinctUntilChanged()
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)
    // 1, 2, 3 출력

우리가 Observable 활용하여 API 호출을 할 때 중복 호출을 방지할 수 있습니다.

 

 

- skip & take

'skip' 지정된 개수의 항목을 건너뜁니다. 'take' 지정된 개수의 항목만 유지합니다.

Observable.of(1, 2, 3, 4, 5)
    .skip(2)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)
// 3, 4, 5 출력

Observable.of(1, 2, 3, 4, 5)
    .take(2)
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)
// 1, 2 출력

 

- throttle ☆

지정한 시간동안 처음 들어오는 이벤트를 방출합니다.

-> 처음 들어오는 이벤트 외 일정시간 동안 뒤에 들어오는 이벤트는 무시

button.rx.tap
    .throttle(.seconds(1), scheduler: MainScheduler.instance)
    .bind {
        print("tap")
    }
    .disposed(by: disposeBag)
    // 1초동안 500번 tap해도 처음 입력한 tap에만 반응한다.

버튼의 중복 입력을 막는데 효율적인 오퍼레이터입니다.

 

 

 

- debounce

throttle과 비슷하지만 지정한 시간동안 들어오는 이벤트 중 마지막 이벤트를 방출합니다.

-> 특정 시간 동안 이벤트가 들어오면 마지막 이벤트만 실행하고 나머지는 무시

button.rx.tap
    .debounce(.seconds(1), scheduler: MainScheduler.instance)
    .bind {
        print("tap")
    }
    .disposed(by: disposeBag)
  	// 1초동안 500번 tap해도 마지막 이벤트만 반응

 

Combining Observables

- combineLatest ☆

 여러 개의 옵저버블로부터 최신 값을 결합합니다.

let bird = PublishSubject<String>()
let mammal = PublishSubject<String>()

Observable.combineLatest( bird, mammal)
    .subscribe(onNext: { bird, mammal in
        print("\(bird) + \(mammal)")
    })
    .disposed(by: disposeBag)

bird.onNext("잉꼬")
mammal.onNext("호랑이")
bird.onNext("참새")
bird.onNext("까치")
mammal.onNext("사자")
// 잉꼬 + 호랑이, 참새 + 호랑이, 까치 + 호랑이, 까치 + 사자 출력

중요한 점은 둘 중 하나의 subject에서 이벤트를 방출해도 동작한다는 점입니다. 최초 잉꼬는 mammal Subject에 방출한 이벤트가 없어서 동작하지 않았습니다.

 

- zip

두 개의 옵저버블을 병렬로 결합합니다.

let bird = PublishSubject<String>()
let mammal = PublishSubject<String>()

Observable.zip( bird, mammal)
    .subscribe(onNext: { bird, mammal in
        print("\(bird) + \(mammal)")
    })
    .disposed(by: disposeBag)

bird.onNext("잉꼬")
mammal.onNext("호랑이")
bird.onNext("참새")
bird.onNext("까치")
mammal.onNext("사자")

// 잉꼬 + 호랑이, 참새 + 사자 출력

자신의 짝이 와야 출력합니다. 잉꼬는 호랑이가 들어왔으니 출력하였고 참새는 기다렸다가 사자가 들어왔을 때 출력하였습니다. 까치는 짝이 들어오지 않아서 출력하지 않았네요

 

- merge

여러개의 옵저버블을 하나의 옵저버블로 만듭니다.

let bird = PublishSubject<String>()
let mammal = PublishSubject<String>()

Observable.merge( bird, mammal)
    .subscribe(onNext: { animal in
        print(animal)
    })
    .disposed(by: disposeBag)

bird.onNext("잉꼬")
mammal.onNext("호랑이")
bird.onNext("참새")
bird.onNext("까치")
mammal.onNext("사자")
// 잉꼬, 호랑이, 참새, 까치, 사자 출력

 

 

- withLatestFrom ☆

두 개의 옵저버블을 조합하고, 하나의 옵저버블의 최신 값을 사용하여 다른 옵저버블을 변환하는 데 사용합니다.

무슨말이지 어려우니까 코드로 보여드릴게요.

    let bird = PublishSubject<String>()

    button.rx.tap
        .withLatestFrom(bird)
        .subscribe(onNext: { name in
            let greeting = "Hello, \(name)!"
            print(greeting)
        })
        .disposed(by: disposeBag)

    bird.onNext("잉꼬")

    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        bird.onNext("앵무새")
    }

    DispatchQueue.main.asyncAfter(deadline: .now() + 6) {
        bird.onNext("참새")
    }
    
    // 잉꼬 출력 -> 앵무새 출력 -> 참새 출력

 

 

버튼을 누를 때 마다 가장 최근에 들어온 bird 이벤트를 출력합니다. 정리하자면 하나의 옵저버블이 새로운 이벤트가 들어 올 때 withLatestFrom에 넣어둔 옵저버블의 최신 이벤트를 사용해서 동작하는 겁니다. 그러니 bird 이벤트가 아무리 들어와도 출력은 안 되겠죠.