Combining

๐Ÿงฉ Combining Operators โ€” ์—ฌ๋Ÿฌ ์ŠคํŠธ๋ฆผ์„ โ€˜์กฐํ•ฉโ€™ํ•˜๊ธฐ

โ€œTwo (or more) Observables enter, one leaves.โ€

Combining ์˜คํผ๋ ˆ์ดํ„ฐ๋Š” ๋‹ค์ˆ˜ Observable์„ ํ•˜๋‚˜๋กœ ํ•ฉ์ณ ํ’๋ถ€ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜, ์ด๋ฒคํŠธ ์ˆœ์„œ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. UIยท๋„คํŠธ์›Œํ‚นยท๋™๊ธฐํ™” ๋กœ์ง์—์„œ ํ•ต์‹ฌ ๋„๊ตฌ๋กœ ์“ฐ์ž…๋‹ˆ๋‹ค.


1๏ธโƒฃ ๋Œ€ํ‘œ Combining ์˜คํผ๋ ˆ์ดํ„ฐ ํ‘œ

Operator
ํ•ต์‹ฌ ์—ญํ• 
ํŠน์ง• ์š”์•ฝ

merge

์—ฌ๋Ÿฌ ์ŠคํŠธ๋ฆผ ์ด๋ฒคํŠธ๋ฅผ ๋™์‹œ์— ๋ฐฉ์ถœ

์ˆœ์„œ ๋ณด์žฅ X, ์ตœ๋Œ€ ๋™์‹œ n๊ฐœ ์ œ์–ด merge(maxConcurrent:)

concat

์ง๋ ฌ ์—ฐ๊ฒฐ, ์•ž ์ŠคํŠธ๋ฆผ ์™„๋ฃŒ ํ›„ ๋‹ค์Œ ๊ตฌ๋…

์ˆœ์„œ ๋ณด์žฅ, back-pressure ์ œ์–ด ์‰ฌ์›€

concatMap

flatMap+concat (Transform+์ง๋ ฌ)

๊ฐ ์š”์†Œโ†’Observable ์ง๋ ฌ ์‹คํ–‰

combineLatest

๊ฐ ์ŠคํŠธ๋ฆผ์˜ ์ตœ์‹ ๊ฐ’ ๊ฒฐํ•ฉ

๋ชจ๋“  ์†Œ์Šค 1๊ฐœ ์ด์ƒ ๊ฐ’ ํ›„ ์‹œ์ž‘

zip

๋™์ผ ์ธ๋ฑ์Šค๋ผ๋ฆฌ ์Œ ์ƒ์„ฑ

์ง ๋งž์ง€ ์•Š์œผ๋ฉด ๋Œ€๊ธฐ, ๊ฐœ์ˆ˜ = ์ตœ์†Œ(stream)

withLatestFrom

ํŠธ๋ฆฌ๊ฑฐ ์ŠคํŠธ๋ฆผ์— ์ตœ์‹  ํŒŒ๋ผ๋ฏธํ„ฐ ์ฃผ์ž…

๋ฒ„ํŠผ ํƒญ + ์ตœ์‹  ํผ ๊ฐ’

sample

์ง€์ • ์ฃผ๊ธฐ/์ŠคํŠธ๋ฆผ ์‹œ, ์ตœ์‹ ๊ฐ’ ์ƒ˜ํ”Œ๋ง

ํด๋ง UI Throttle

amb

๋จผ์ € ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ ์ŠคํŠธ๋ฆผ๋งŒ ์‚ฌ์šฉ

๋ ˆ์ด์Šค Winner ์„ ํƒ

switchLatest

๋‚ด๋ถ€ Observable ์ตœ์‹  ๊ฒƒ๋งŒ ๊ตฌ๋…

flatMapLatest + identity


2๏ธโƒฃ ์‹ค์ „ ์Šค๋‹ˆํŽซ

A. combineLatest โ€” ์ด๋ฉ”์ผ & ๋น„๋ฐ€๋ฒˆํ˜ธ ์œ ํšจ์„ฑ

Observable.combineLatest(emailValid, passwordValid) { $0 && $1 }
    .bind(to: loginButton.rx.isEnabled)

B. merge โ€” ๋‹ค์ค‘ ์„ผ์„œ ๋ฐ์ดํ„ฐ ํ†ตํ•ฉ

Observable.merge(accelerometer, gyroscope, magnetometer)
    .subscribe(onNext: processMotion)

C. zip โ€” ํ‰ํ–‰ ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๊ฒฐ๊ณผ ๋งคํ•‘

Observable.zip(api.userInfo(), api.userPosts()) { (user, posts) in
    ProfileViewModel(user: user, posts: posts)
}

D. withLatestFrom โ€” โ€˜์ „์†กโ€™ ๋ฒ„ํŠผ + ์ตœ์‹  ํ…์ŠคํŠธ

sendButton.rx.tap
    .withLatestFrom(messageField.rx.text.orEmpty)
    .filter { !$0.isEmpty }
    .bind(to: viewModel.sendMessage)

3๏ธโƒฃ ์˜ค๋ฅ˜ ์ „ํŒŒ ๊ทœ์น™

Operator
์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๋™์ž‘

merge

์ฆ‰์‹œ ์ „ํŒŒ(fail-fast) โ€” ๋‹ค๋ฅธ ์ŠคํŠธ๋ฆผ ๊ตฌ๋… ํ•ด์ œ

concat

์•ž ์ŠคํŠธ๋ฆผ ์—๋Ÿฌ โ†’ ์ „ํŒŒ & ์‹œํ€€์Šค ์ข…๋ฃŒ

combineLatest/zip

์–ด๋А ํ•˜๋‚˜ ์—๋Ÿฌ โ†’ ์ „ํŒŒ & ์ข…๋ฃŒ

amb

์„ ํƒ๋œ ์ŠคํŠธ๋ฆผ ์—๋Ÿฌ๋งŒ ๊ณ ๋ ค

์—๋Ÿฌ ๋ณต๊ตฌ๋Š” catchError, retry (๋‹ค์Œ ์žฅ)๋กœ ์ฒ˜๋ฆฌ.


4๏ธโƒฃ ์„ฑ๋Šฅ & ๋ฉ”๋ชจ๋ฆฌ ๊ณ ๋ ค

  • combineLatest๋Š” ๊ฐ ์†Œ์Šค์˜ ์ตœ์‹ ๊ฐ’์„ ์บ์‹œ โ†’ ํฐ ๊ฐ์ฒด๋Š” ์ฐธ์กฐ ์ „๋‹ฌ or map ์ถ•์†Œ.

  • merge ๋™์‹œ์„ฑ ๋ฌด์ œํ•œ ์‹œ ์ด๋ฒคํŠธ ํญ๋ฐœ ๊ฐ€๋Šฅ โ†’ maxConcurrent ์„ค์ •.

  • zip์€ ์ง ๋งž์ถ”๊ธฐ ์œ„ํ•ด ๋ฒ„ํผ ์‚ฌ์šฉ โ†’ ๋น„๋Œ€์นญ ์ŠคํŠธ๋ฆผ ์†๋„๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์ฆ๊ฐ€.


5๏ธโƒฃ ํŒจํ„ด ๋ณ„ Best Operator

์‹œ๋‚˜๋ฆฌ์˜ค
์ถ”์ฒœ Operator
์ด์œ 

ํผ ์ž…๋ ฅ 2๊ฐœ+ ๋ฒ„ํŠผ ํ™œ์„ฑ

combineLatest

์ตœ์‹  ์ƒํƒœ ๊ฒฐํ•ฉ

ํŒŒ์ผ Aโ†’Bโ†’C ์ˆœ์ฐจ ์—…๋กœ๋“œ

concatMap

์ง๋ ฌ ๋ณด์žฅ + ๋ณ€ํ™˜

์„œ๋ฒ„1/2 ์ค‘ ๋น ๋ฅธ ์‘๋‹ต ์‚ฌ์šฉ

amb

๋ ˆ์ด์Šค ์Šน์ž ์ŠคํŠธ๋ฆผ ์ฑ„ํƒ

์ฃผ๊ธฐ์  Heartbeat + ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ

merge

์„œ๋กœ ๋…๋ฆฝ ๋™์‹œ ์ฒ˜๋ฆฌ


6๏ธโƒฃ Mini Quiz

  1. merge์™€ combineLatest ์ฐจ์ด๋ฅผ ์ด๋ฒคํŠธ ํ•จ์ˆ˜ ๊ด€์ ์—์„œ ์„ค๋ช…ํ•ด ๋ณด๋ผ.

  2. zip ์‚ฌ์šฉ ์‹œ ๋‘ ์†Œ์Šค์˜ ์ด๋ฒคํŠธ ์†๋„๊ฐ€ ํฌ๊ฒŒ ๋‹ค๋ฅด๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€? ๋Œ€์‘์ฑ…์€?

  3. withLatestFrom๊ณผ sample์˜ ์ฐจ์ด ํ•œ ์ค„ ์š”์•ฝ.

Answers
  1. merge๋Š” onNext๋ฅผ interleaveํ•˜์—ฌ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ, ๊ฒฐํ•ฉ ํ•จ์ˆ˜ ์—†์Œ. combineLatest๋Š” ๊ฐ ์†Œ์Šค onNext ์‹œ ๊ฒฐํ•ฉ ํด๋กœ์ €๋ฅผ ํ˜ธ์ถœํ•ด 1๊ฐœ์˜ ์ƒˆ ๊ฐ’ ์ƒ์„ฑ.

  2. ๋น ๋ฅธ ์ŠคํŠธ๋ฆผ ์ด๋ฒคํŠธ๊ฐ€ ๋ฒ„ํผ์— ์Œ“์—ฌ ๋ฉ”๋ชจ๋ฆฌ ์ฆ๊ฐ€; zipLatest ๋Œ€์•ˆ(์—†๋‹ค๋ฉด combineLatest) ๋˜๋Š” buffer/skip๋กœ ์†๋„ ๋งž์ถ”๊ธฐ.

  3. withLatestFrom์€ ํŠธ๋ฆฌ๊ฑฐ ์ŠคํŠธ๋ฆผ onNext ๋•Œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ŠคํŠธ๋ฆผ ์ตœ์‹ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ณ , sample์€ ์ง€์ • ๊ฐ„๊ฒฉ/ํŠธ๋ฆฌ๊ฑฐ ์‹œ ์ตœ์‹ ๊ฐ’๋งŒ ์ „๋‹ฌ(ํŠธ๋ฆฌ๊ฑฐ๊ฐ’ ๋ฌด์‹œ).


๋‹ค์Œ โ–ถ๏ธ timeBased ๋กœ ์ด๋™ํ•ด debounce, throttle, timeout ๋“ฑ ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ์˜คํผ๋ ˆ์ดํ„ฐ๋ฅผ ์‚ดํŽด๋ด…์‹œ๋‹ค. ๐Ÿš€

Last updated