Jimmy's iOS
5. Combining Operator 알아보기 본문
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 |