๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐ŸŽ iOS/Combine

[Combine] Combine ์‹œ์ž‘ํ•˜๊ธฐ - Publisher์™€ Subscriber

์•ˆ๋…•ํ•˜์„ธ์š” ์ œ์ธ์ž…๋‹ˆ๋‹ค!

๊ต‰์žฅํžˆ ์˜ค๋žœ๋งŒ์— ๊ธ€์„ ์“ฐ๋Š”๋ฐ... ์ปด๋ฐ”์ธ์œผ๋กœ ๋Œ์•„์™”์Šต๋‹ˆ๋‹ค ใ…Ž...

๋™์•„๋ฆฌ ํ”Œ์ ์œผ๋กœ ๋ฐ”๋น ์„œ ๋ธ”๋กœ๊ทธ์— ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ ์ •๋ฆฌ๋ฅผ ๋ชปํ–ˆ๋Š”๋ฐ,

๋™์•„๋ฆฌ ํ›„๊ธฐ๋„ ๊ฐ€์ ธ์˜ค๊ณ  ๊ทธ๋™์•ˆ ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ๋„ ์ •๋ฆฌํ•ด์„œ ์˜ฌ๋ ค๋ณด๋„๋ก ํ• ๊ฒŒ์š”!

๋™์•„๋ฆฌ ํ”Œ์ ์—์„œ SwiftUI + Combine ์œผ๋กœ ๊ฐœ๋ฐœ์„ ํ•˜๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ์š”, ์ƒˆ๋กœ์šด ์Šคํƒ์— ๋„์ „ํ•˜๋‹ˆ ์žฌ๋ฐŒ์—ˆ์Šต๋‹ˆ๋‹ค๐Ÿ˜†

๋” ๊นŠ๊ฒŒ ๊ณต๋ถ€ํ•ด๋ณด๋ฉด์„œ ๋‚ด์šฉ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด๋ ค๊ตฌ์š” ! ์˜ค๋Š˜์€ ์ปด๋ฐ”์ธ ์‹œ์ž‘ํ•˜๊ธฐ๋กœ Publisher์™€ Subscriber์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Combine ์ด๋ž€?

Combine์€ Apple์—์„œ ๊ณต์‹์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.

๋˜ ๋‹ค๋ฅธ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋„๊ตฌ์ธ RxSwift๋Š” third-party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด์ง€๋งŒ Combine์€ Apple์—์„œ ๊ณต์‹์ ์œผ๋กœ ์ œ๊ณตํ•ด์ฃผ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, 2019๋…„์— ๊ณต๊ฐœ๋˜์–ด iOS 13 ์ด์ƒ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค!

 

๊ณต์‹๋ฌธ์„œ์˜ ์†Œ๊ฐœ๋ฅผ ๋ฒˆ์—ญํ•ด๋ณด๋ฉด,

Combine์€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์—ฐ์‚ฐ์ž๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ ์ •๋„๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒ ๋„ค์š”!

๊ฐœ์š”

Combine ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๊ฐ’ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์„ ์–ธ์  Swift API๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ฐ’๋“ค์€ ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Combine์€ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์„ ๋ฐฉ์ถœํ•˜๋Š” publisher์™€ publisher๋กœ๋ถ€ํ„ฐ ๊ฐ’๋“ค์„ ์ „๋‹ฌ๋ฐ›๋Š” subscriber๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

 

Publisher ํ”„๋กœํ† ์ฝœ์€ ์ผ๋ จ์˜ ๊ฐ’์„ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

Publisher๋Š” upstream publisher๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๊ฐ’์— ๋Œ€ํ•ด ํ–‰๋™ํ•˜๊ณ  ์ด ๊ฐ’๋“ค์„ republishํ•˜๋Š” ์—ฐ์‚ฐ์ž๋“ค์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

 

Subscriber๋Š” publisher ์ฒด์ธ์˜ ๋์—์„œ ๊ฐ’๋“ค์„ ์ˆ˜์‹ ๋ฐ›์„ ๋•Œ ๋™์ž‘๋ฉ๋‹ˆ๋‹ค.

subscriber๋“ค์ด ๋ช…์‹œ์ ์œผ๋กœ ๊ฐ’์„ ์š”์ฒญํ•  ๋•Œ๋งŒ publisher๋“ค์€ ๊ฐ’์„ ๋ฐฉ์ถœํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“œ๋Š” subscribers ์ฝ”๋“œ๋Š” ์—ฐ๊ฒฐ๋œ publisher๋“ค๋กœ๋ถ€ํ„ฐ ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›๋Š”์ง€ ํ†ต์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Timer, NotificationCenter ๋ฐ URLsession์„ ํฌํ•จํ•œ ์—ฌ๋Ÿฌ Foundation ์œ ํ˜•์€ publisher๋ฅผ ํ†ตํ•ด ๊ธฐ๋Šฅ์„ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ, Combine์€ Key-Value Observing์„ ์ค€์ˆ˜ํ•˜๋Š” ๋ชจ๋“  ์†์„ฑ์— ๋Œ€ํ•œ ๋‚ด์žฅ๋œ publisher๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

 

์—ฌ๋Ÿฌ publisher์˜ output์„ ๊ฒฐํ•ฉํ•˜์—ฌ ์ƒํ˜ธ ์ž‘์šฉ์„ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด text field์˜ publisher๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ํ…์ŠคํŠธ ๊ฐ’์˜ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ URL ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๋‹ค๋ฅธ publisher๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์•ฑ์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Combine์„ ์ฑ„ํƒํ•˜๋ฉด ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๋ฅผ ์ค‘์•™ ์ง‘์ค‘ํ™”ํ•˜๊ณ  ์ค‘์ฒฉ ํด๋กœ์ €๋‚˜ ๋‹ค๋ฅธ ํƒ€์ž…๋“ค์„ ๊ฐ€์ง„ ์ฝœ๋ฐฑ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ์ œ๊ฑฐํ•จ์œผ๋กœ์จ ์ฝ”๋“œ๋ฅผ ์ฝ๊ณ  ์œ ์ง€ํ•˜๊ธฐ๊ฐ€ ์‰ฌ์›Œ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ๊นŒ์ง€ ๊ณต์‹๋ฌธ์„œ ์„ค๋ช…์„ ๋ฒˆ์—ญํ•˜๋ฉฐ Combine์ด ๋ฌด์—‡์ธ์ง€ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

Combine์˜ ํ•ต์‹ฌ์ด ๋˜๋Š” Publisher์™€ Subscriber์— ๋Œ€ํ•ด ์•Œ์•„๋ด…์‹œ๋‹ค!

Publisher

์‹œ๊ฐ„์ด ํ๋ฆ„์— ๋”ฐ๋ผ ์ผ๋ จ์˜ ๊ฐ’๋“ค์„ ์ „์†ก(๋ฐฉ์ถœ)ํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์„ ์„ ์–ธํ•˜๋Š” ํ”„๋กœํ† ์ฝœ

protocol Publisher<Output, Failure>

 

ํ•˜๋‚˜์˜ publisher๋Š” ํ•˜๋‚˜ ํ˜น์€ ์—ฌ๋Ÿฌ subscriber ์ธ์Šคํ„ด์Šค์—๊ฒŒ ์š”์†Œ๋“ค์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

subscriber์˜ Input๊ณผ Failure (associated type)๋Š” ๊ฐ๊ฐ publisher์˜ Output ๊ณผ Failure ์— ๋งค์น˜๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

publisher๋Š” subscriber๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด receive(subscriber:) ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ receive(subscriber:)
ํŠน์ •ํ•œ subscriber๋ฅผ ํ•ด๋‹น publisher์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input

 

subscriber ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ตฌ๋…์ž๊ฐ€ ์—ฐ๊ฒฐ๋˜๊ณ , ๊ฐ’์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Publisher์˜ ๊ตฌํ˜„๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋ฐ˜๋“œ์‹œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด publisher๋Š” subscriber์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

  • receive(subscription:): ๊ตฌ๋…์ž์—๊ฒŒ ๊ตฌ๋…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ด๋ฃจ์–ด์กŒ๊ณ  ์•„์ดํ…œ๋“ค์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Œ์„ ์•Œ๋ ค์ฃผ๋Š” ๋ฉ”์„œ๋“œ. ๊ตฌ๋… ์š”์ฒญ์„ ์Šน์ธํ•˜๊ณ  Subscription ์„ ๋ฐ˜ํ™˜ํ•จ. ๊ตฌ๋…์ž๋Š” subscription์„ ์ด์šฉํ•ด publisher์—๊ฒŒ element๋ฅผ ์š”๊ตฌํ•˜๊ณ  ์ด๋ฅผ ์ด์šฉํ•ด publishing ์„ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • receive(_:): ํ•˜๋‚˜์˜ ์š”์†Œ๋ฅผ ๊ตฌ๋…์ž์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค.
  • receive(completion:): publishing์ด ์ •์ƒ์ ์œผ๋กœ ๋˜๋Š” ์˜ค๋ฅ˜๋กœ ์ข…๋ฃŒ๋˜์—ˆ์Œ์„ ๊ตฌ๋…์ž์—๊ฒŒ ์•Œ๋ฆฐ๋‹ค.

downstream ๊ตฌ๋…์ž(subscriber)๋“ค์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋ ค๋ฉด ๋ชจ๋“  publisher๊ฐ€ ํ•ด๋‹น ํ”„๋กœํ† ์ฝœ(=Publisher)์„ ์ค€์ˆ˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Publisher์— ๋Œ€ํ•œ extentension์€ ์ •๊ตํ•œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ฒด์ธ์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๊ตฌ์„ฑํ•˜๋Š” ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ ์—ฐ์‚ฐ์ž๋Š” Publisher ํ”„๋กœํ† ์ฝœ์„ ๊ตฌํ˜„ํ•˜๋Š” ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ํƒ€์ž…๋“ค ๋Œ€๋ถ€๋ถ„์€ Publishers ์—ด๊ฑฐํ˜•์— ๋Œ€ํ•œ ํ™•์žฅ์œผ๋กœ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

Subscriber

publisher ๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์„ ์„ ์–ธํ•˜๋Š” ํ”„๋กœํ† ์ฝœ

protocol Subscriber<Input, Failure> : CustomCombineIdentifierConvertible

 

Subscriber ์ธ์Šคํ„ด์Šค๋Š” Publisher๋กœ๋ถ€ํ„ฐ ๋‘˜ ์‚ฌ์ด์˜ ๊ด€๊ณ„์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์„ ์„ค๋ช…ํ•˜๋Š” life cycle ์ด๋ฒคํŠธ์™€ ํ•จ๊ป˜ ๊ฐ’์˜ ์ŠคํŠธ๋ฆผ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.

subscriber์˜ Input๊ณผ Failure associated type์€ ์—ฐ๊ฒฐ๋œ publisher์˜ Output๊ณผ Failure ์™€ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

publisher์˜ subscribe(_:) ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•จ์œผ๋กœ์จ subscriber๋ฅผ publisher์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ ํ›„์—๋Š” publisher๊ฐ€ subscriber์˜ receive(subscription:) ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

์ด๋Š” subscriber์—๊ฒŒ ๊ฐ’์„ ์š”์ฒญํ•˜๊ณ  ์„ ํƒ์ ์œผ๋กœ ๊ตฌ๋…์„ ์ทจ์†Œํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” Subscription ์ธ์Šคํ„ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

subsriber๊ฐ€ ์ตœ์ดˆ์˜ ์š”์ฒญ์„ ํ•œ ํ›„์—๋Š” ์ƒˆ๋กญ๊ฒŒ ๊ฒŒ์‹œ๋˜๋Š” ์š”์†Œ๋“ค์„ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด publisher๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ receive(_:)๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

publisher๊ฐ€ publishing์„ ์ค‘๋‹จํ•˜๊ฒŒ ๋˜๋ฉด, subscriber๋Š” Subsribers ํƒ€์ž…์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ receive(completion:)์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. Completion์€ publishing์ด ์ •์ƒ์ ์œผ๋กœ ์™„๋ฃŒ๋˜๋Š”์ง€ ํ˜น์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

 

Publisher ํƒ€์ž…์— ๋Œ€ํ•œ ์—ฐ์‚ฐ์ž๋กœ ์•„๋ž˜์˜ subsriber๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

 

  • sink(receiveComplete:receiveValue:): ์™„๋ฃŒ ์‹ ํ˜ธ์™€ ์ƒˆ ์š”์†Œ๋ฅผ ์ˆ˜์‹ ํ•  ๋•Œ๋งˆ๋‹ค ์ž„์˜์˜ ํด๋กœ์ €๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • assign(to:on :): ์ƒˆ๋กœ ๋ฐ›์€ ๊ฐ’์„ keyPath์— ๋”ฐ๋ผ ์ฃผ์–ด์ง„ ์ธ์Šคํ„ด์Šค์˜ property์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€ Combine์˜ ๊ธฐ๋ณธ์ ์ธ ๋‚ด์šฉ์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด๋ณด์•˜๋Š”๋ฐ์š”!

ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ์„ ํ†ตํ•ด ์ ์šฉํ•˜๊ฒŒ ๋˜๋Š” ๋‚ด์šฉ๋“ค๋„ ์ •๋ฆฌํ•˜๋ฉด์„œ ๊ตฌ์ฒด์ ์ธ ์‚ฌ์šฉ๋ฐฉ๋ฒ•, ์˜ˆ์‹œ์— ๋Œ€ํ•ด์„œ๋„ ๊ฐ€์ ธ์™€๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค~!!


[์ฐธ๊ณ  ์ž๋ฃŒ]

https://developer.apple.com/documentation/combine

https://ios-development.tistory.com/1113

https://developer.apple.com/documentation/combine/publisher

https://brunch.co.kr/@tilltue/65