Jimmy's iOS

5. Combining Operator 알아보기 본문

RxSwift

5. Combining Operator 알아보기

Jimmy Youn 2021. 12. 11. 18:24

Combining Operators 

  • filtering 이나 transforming operator (map..) 처럼 시퀀스의 출력값을 핸들링해서 결과로 내뿜는것은 동일하지만 여러가지 시퀀스를 조합한다는 점이 다르다. 
  • 다수의 시퀀스를 어떻게 묶고 조합해서 새로운 결과값으로 만들 수 있는지 도와주는 연산자이다. 

 

1. startWith 

- 초기값을 받는지 여부가 옵저버블을 작업할 때 중요하게 확인해볼 것중 하나인데, 예를 들면 현재 위치, 네트워크 연결 상태같이, 초기값이 필요한 상태가 있다. 이럴때는 현재 상태와 함께 초기값을 붙일 수 있는데 그럴때 사용하는 것이 startWith 이다. 

- startWith 는 좀 더 일반적인 concat의 변형

 

let disposeBag = DisposeBag()

let yellowTeam = Observable<String>.of("Jimmy", "Jason", "Miler")

yellowTeam.startWith("Teacher") // 동일한 String 타입이 들어가야한다.
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)
  
  
// Teacher -> Teacher 가 초기값으로 맨처음 나온다.
// Jimmy
// Jason
// Miler


yellowTeam
  .enumerated()
  .map{ index, element in
    return element + " 학생" + "\(index)"
  }
  .startWith("Teacher") // 동일한 String 타입이 들어가야한다. // startWith 는 어느 위치든 항상 먼저
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)
  
// Teacher
// Jimmy 학생0
// Jason 학생1
// Miler 학생2

 

2. concat 

- 두개 또는 여러개의 Observable 을 연결해주는 역할의 연산자

- 콜렉션과 콜렉션, 시퀀스와 시퀀스 간의 조합으로 만들 수 있다.  

 

let disposeBag = DisposeBag()


print("---------concat1-------") 

let redTeam = Observable<String>.of("Karlo", "Miranda", "Kelly")
let teacher = Observable<String>.of("Teacher")

let walkWithTeam = Observable
  .concat([teacher, redTeam]) // 위에서 만든 observable 2개를 array에 넣어서 concat으로 표현

walkWithTeam.subscribe(onNext: {
  print($0)
}).disposed(by: disposeBag)


// Teacher // startWith 와 동일하게 Teacher 먼저 나온다. 
// Karlo
// Miranda
// Kelly


print("-----------concat2---------") // teacher 에 concat 을 써서 second 시퀀스를(reaTeam) 넣을 수 있다.
teacher.concat(redTeam)
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)
  
// Teacher // startWith 와 동일하게 Teacher 먼저 나온다. 
// Karlo
// Miranda
// Kelly

 

3. concatMap

- 각각의 시퀀스가 다음 시퀀스가 구독되기 전에 합쳐지는 것을 보장한다.

let disposeBag = DisposeBag()

let children : [String : Observable<String>] = [
  "yellowteam" : Observable.of("kid1", "kid2", "kid3"),
  "redteam" : Observable.of("kid4", "kid5")
] 

Observable.of("yellowteam", "redteam")
  .concatMap { team in
    children[team] ?? .empty()
  }
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag) // yellowteam 에 해당하는게 먼저 나오고, redteam 이 다 출력된다.


// kid1
// kid2
// kid3
// kid4
// kid5

 

4. merge 

- 시퀀스를 합친다. 

- n 개의 옵저버블을 합쳐서 구독하도록 하는거다. 어떤 값이 나올지 순서를 보장하진 않는다. 둘중 하나라도 에러를 방출하게 되면 merge 에 묶인 전체 observable 자체가 즉시 에러를 방출하고 종료를 하게 된다.

 

let disposeBag = DisposeBag()

let north = Observable.from(["강북구", "성북구", "동대문구", "종로구"])
let south = Observable.from(["강남구", "강동구", "영등포구", "양천구"])

Observable.of(north, south)
  .merge()
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag) // 2가지 옵저버블이 순서 상관 없이 나온다.


// 강북구
// 성북구
// 강남구
// 동대문구
// 강동구
// 종로구
// 영등포구
// 양천구


print("---------merge2 maxConcurrent-----------") // 순서를 보장한거처럼 보이는데

Observable.of(north, south)
  .merge(maxConcurrent: 1) // merge 라는 걸 통해서 한번에 받아낼 observable의 수를 의미한다. 하나로 제한하면 첫번째로 구독을 시작하게 된 observable 이 전부 도착해서 element 를 방출하기 전까지 동시에 다른 observable 을 받지 않는다. 제한을 두는 merge 를 쓰는 가능성은 낮으나 네트워크 요청이 많아질때 리소스를 제한하거나 연결수를 제한하기 위해서 maxConcurrnet 를 사용할 수 있다.
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)
  
// 강북구
// 성북구
// 동대문구
// 종로구
// 강남구
// 강동구
// 영등포구
// 양천구

 

5. combineLatest  

- RxSwift 에서 결합 연산자로써 주요하게 사용된다. 

- 각 Observable에서 방출되는 요소들중 가장 최근 요소들끼리 결합해서 구독자에게 전달해주는 연산자.

- 여러 텍스트 필드를 한번에 관찰하고 값을 결합하거나 여러 소스들의 상태들을 보든 앱이 있을때 많이 사용하게 된다.

 

let disposeBag = DisposeBag() 

print("----------combineLatest------------")
let lastName = PublishSubject<String>()
let firstName = PublishSubject<String>()

let name = Observable.combineLatest(lastName, firstName) { lastname, firstname in
  lastname + firstname
}

name
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)

lastName.onNext("김")
firstName.onNext("박사")
firstName.onNext("영수")
firstName.onNext("은영")
lastName.onNext("박")
lastName.onNext("이")
lastName.onNext("조")

// 김박사
// 김영수
// 김은영
// 박은영
// 이은영
// 조은영

// 각각에 받은 성과 이름의 시퀀스들의 최종값들을 받게 된다. 
// 김씨가 처음 방출되었지만 이름의 최초 '박사' 가 방 출될때 까지 기다린다. 
// 그다음 '영수'가 방출될때 성의 최신값은 '김'이고 이름의 최신값은 '영수' 이니까 결합해서 나오게 된다. 
// '영수'라는 이름이 방출될때는 '박사'라는 이름이 나오지 않는다. 왜냐하면 이름의 최신값이 영수 이기 때문이다.
// 그러다 성이 새로운 이벤트 '박' 을 방출하면 성의 최신 '박' 과 이름의 최신 '은영' 이 방출된다.



print("----------combineLatest2------------") // 최대 8개까지의 source 들로 구성할 수 있다.

let dateForm = Observable<DateFormatter.Style>.of(.short, .long)

let nowDate = Observable<Date>.of(Date())

let showNowDate = Observable
  .combineLatest(dateForm, nowDate) { form, nowdate -> String in
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = form
    return dateFormatter.string(from: nowdate)
  }


showNowDate.subscribe(onNext: {
  print($0)
}).disposed(by: disposeBag) // 숏타입, 롱타입 각각 표시가 된다.


// 12/11/21
// December 11, 2021


print("----------combineLatest3------------")

let lastName2 = PublishSubject<String>()
let firstName2 = PublishSubject<String>()

let fullName = Observable
  .combineLatest([firstName2, lastName2]) { name in // collection 타입으로 방출할 수 있다.
    name.joined(separator: " ")
  }

fullName
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)

lastName2.onNext("Kim")
firstName2.onNext("Paul")
firstName2.onNext("Stella")
firstName2.onNext("Lily")

// Paul Kim
// Stella Kim
// Lily Kim

 

6. zip 

- 순서를 보장하면서 하나씩 합쳐진다. 
- 둘중 하나의 observable 이 완료되면 zip 전체가 완료된다.

print("----------zip------------")

enum Victory {
  case win
  case lose
}


let competition = Observable<Victory>.of(.win, .win, .lose, .win, .lose)

let athlete = Observable<String>.of("한국", "스위스", "미국", "브라질", "일본", "중국")

let competionResult = Observable
  .zip(competition, athlete) { result, person in
    return person + "선수 " + "\(result)"
  }

competionResult.subscribe(onNext: {
  print($0)
}).disposed(by: disposeBag) 

// 한국선수 win
// 스위스선수 win
// 미국선수 lose
// 브라질선수 win
// 일본선수 lose


// 순서를 보장하면서 하나씩 합쳐진다. 
// 중국에 대한 연산 결과가 없는데 둘중 하나의 observable 이 완료되면 zip 전체가 완료된다.

 

7. withLatestFrom 

- 트리거 성격을 띄는, 어떠한 옵저버블 중 하나가 트리거 역할을 한 뒤에 조합을 시작하는 combine operator

- 트리거가 발생된 시점에서 가장 최신의 값만 보낸다. 

let disposeBag = DisposeBag() 

let trigger = PublishSubject<Void>()
let runner = PublishSubject<String>()

trigger.withLatestFrom(runner)
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)

runner.onNext("Jimmy")
runner.onNext("Kim Tony")
runner.onNext("Selly Wony Uber")

trigger.onNext(Void())
trigger.onNext(Void()) // 맨 마지막(최신) onNext 가 2번 발생한다.


// Selly Wony Uber
// Selly Wony Uber

 

8. sample 

- 위의 withLatestFrom 과 거의 똑같이 작동하지만 단 한번만 방출한다는 차이점이 있다. 즉 여러번 트리거를 당겨도 한번만 출력된다.

let disposeBag = DisposeBag() 

let start = PublishSubject<Void>()
let f1RacingPlayer = PublishSubject<String>()

f1RacingPlayer
  .sample(start)
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)

f1RacingPlayer.onNext("🚗")
f1RacingPlayer.onNext("🚗   🚕")
f1RacingPlayer.onNext("🚗      🚕   🚙")

start.onNext(Void())
start.onNext(Void())
start.onNext(Void())


// 🚗      🚕   🚙

 

9. amb 

- switch 역할을 하는 operator, amb 는 모호한 의미의 약자이다

- 두가지 시퀀스를 받을때 두가지 시퀀스중 어떤걸 받을지 애매모호 할때 사용 

let disposeBag = DisposeBag() 

let bus1 = PublishSubject<String>()
let bus2 = PublishSubject<String>()

let busStop = bus1.amb(bus2) 

busStop
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)

bus2.onNext("버스2 승객0")
bus1.onNext("버스1 승객0")
bus1.onNext("버스1 승객1")
bus2.onNext("버스2 승객1")
bus1.onNext("버스1 승객2")
bus2.onNext("버스2 승객2")

// 버스2 승객0
// 버스2 승객1
// 버스2 승객2

// 자기가 가지고 있는 두가지 옵저버블 모두 구독하긴 하는데 
// 이 2개 중에 어떤것이든 요소를 먼저 방출하는 옵저버가 생기면 나머지에 대해서는 구독을 하지 않는다. 
// 밑에서 bus2 가 먼저 onNext 로 방출을 하고 있기 때문에 bus1 에 대해서는 구독하지 않는다.

10. switchLatest

let disposeBag = DisposeBag() 

let student1 = PublishSubject<String>()
let student2 = PublishSubject<String>()
let student3 = PublishSubject<String>()

let handUpEvent = PublishSubject<Observable<String>>()

let classRoom = handUpEvent.switchLatest()

classRoom.subscribe(onNext: {
  print($0)
}).disposed(by: disposeBag)



handUpEvent.onNext(student1) // 최신은 학생1
student1.onNext("학생1 : 저는 1번 학생입니다.") // 프린트
student2.onNext("학생2 : 저요 저요!") // 무시

handUpEvent.onNext(student2) // 이제 최신은 학생2
student2.onNext("학생2 : 저는 2번 학생입니다.") // 프린트
student1.onNext("학생1 : 아.. 나 아직 할말 있는데")// 무시

handUpEvent.onNext(student3) // 이제 최신은 학생3
student2.onNext("학생2 : 아니 잠깐만! 내가!..") // 무시
student1.onNext("학생1 : 언제 말할 수 있죠?") // 무시
student3.onNext("학생3 : 저는 3번 학생입니다. 제가 이긴거 같네요") // 프린트

handUpEvent.onNext(student1) // 다시 최신의 값은 학생1
student1.onNext("학생1 : 아니 승자는 나야.") // 프린트
student2.onNext("학생2 : ㅠㅠ ") // 무시
student3.onNext("학생3 : 이긴 줄 알았는데..") // 무시
student2.onNext("학생2 : 이거 이기고 지는 손들기였나요?") // 무시



// 목적은 소스(handUpEvent) 옵저버블로 들어온 마지막 시퀀스의 아이템만 구독하는것이 switchLatest의 특징이다.

// 학생1 : 저는 1번 학생입니다.
// 학생2 : 저는 2번 학생입니다.
// 학생3 : 저는 3번 학생입니다. 제가 이긴거 같네요
// 학생1 : 아니 승자는 나야.

 

11. reduce 

- 옵저버블 안에 들어가 있는 엘리먼트들을 결합해주는 것이 reduce 란 형태이다. 

- reduce 의 역할은 제공된 초기값에서 시작해서 소스 값들이 방출될 때마다 값을 가공하고 옵저버블이 완료되었을때 결과값을 방출한다. 

let disposeBag = DisposeBag() 

Observable.from((1...10))
  .reduce(0, accumulator: { summary, newValue in
    return summary + newValue
  })
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)

// 55


// 동일한 방식
Observable.from((1...10))
  .reduce(0, accumulator: +)
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)
  
  // 55

 

12. scan 

- reduce 같은 경우는 결과값만을 방출하지만 scan 은 매번 값이 들어올때마다 변형된 결과값들을 방출한다. 

-scan 의 쓰임은 광범위하다. 총합, 통계, 상태를 계산할때등 다양하게 쓸 수 있다.

Observable.from((1...10))
  .scan(0, accumulator: +)
  .subscribe(onNext: {
    print($0)
  }).disposed(by: disposeBag)


// 1
// 3
// 6
// 10
// 15
// 21
// 28
// 36
// 45
// 55

'RxSwift' 카테고리의 다른 글

6. RxCocoa 알아보기  (0) 2021.12.13
4. Subject 알아보기  (0) 2021.12.02
3. Single, Maybe, Completable 알아보기  (0) 2021.11.30
2. Observable 과 Operator 알아보기  (0) 2021.11.30
1. RxSwift 알아보기  (0) 2021.11.21