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

๐ŸŽ iOS & Swift

[WWDC19] Introducing Combine

๋ณธ ๊ธ€์€ WWDC ๋ฅผ ๋ณด๊ณ , ๋ฒˆ์—ญ ๋ฐ ์š”์•ฝ ๊ทธ๋ฆฌ๊ณ  ์‹คํ–‰ํ•ด๋ณด๋Š” ์Šคํ„ฐ๋”” ํ”„๋กœ์ ํŠธ์˜ ์ผํ™˜์ž…๋‹ˆ๋‹ค.

Introducing Combine

WWDC19์—๋Š” Apple์˜ ์ตœ์‹  Framework์ธ Combine์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐํ•˜์ž๊ณ  ํ•˜๋ฉด์„œ ์„œ๋ง‰์„ ์—ฌ๋Š”๋ฐ์š”. ๊ฐ™์ด ์˜ˆ์‹œ๋ฅผ ๋ณด๊ณ  ์ดํ•ดํ•˜๋ฉด์„œ Combine์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•ฉ์‹œ๋‹ค.

๊ฐ„๋‹จํ•œ ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์„ ์˜ˆ์‹œ๋กœ ๋“ญ๋‹ˆ๋‹ค. ์ด ํ™”๋ฉด์—์„œ์˜ ์š”๊ตฌ์‚ฌํ•ญ์€ ํฌ๊ฒŒ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์šฐ์„  ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์œ ํšจํ•œ์ง€ ๋„คํŠธ์›Œํฌ์— ์š”์ฒญ์„ ํ•ด์„œ ํ™•์ธ์„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„œ๋กœ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ชจ๋“  ์ž‘์—…์€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ฐจ๋‹จ๋˜์ง€ ์•Š๊ณ  ๋ฐ˜์‘ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋„ ์ด๋ฏธ ๋งŽ์€ ๋น„๋™๊ธฐ ์ž‘์—…์ด ์ง„ํ–‰๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Target/Action์„ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋Œ€ํ•œ ์•Œ๋ฆผ์„ ์ˆ˜์‹ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋งŽ์€ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์œผ๋กœ ์„œ๋ฒ„๋ฅผ overwhelmํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด์„œ Timer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅ์„ ์ž ์‹œ ๋ฉˆ์ถœ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  KVO์™€ ๊ฐ™์€ ๊ฒƒ์„ ์‚ฌ์šฉํ•ด์„œ ํ•ด๋‹น ๋น„๋™๊ธฐ ์ž‘์—…์— ๋Œ€ํ•œ ์ง„ํ–‰๋ฅ  ์—…๋ฐ์ดํŠธ๋ฅผ ์ˆ˜์‹ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฆ„๊ณผ ์•”ํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด์„œ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๊ณ  ์œ ํšจํ•œ ๊ฐ’์ธ์ง€๋ฅผ ์ฒดํฌํ•ด์ค๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ์— ๋”ฐ๋ผ UI๋„ ์—…๋ฐ์ดํŠธ ํ•ด์ฃผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Asynchronous Interfaces

  1. Target/Action
  2. Notification center
  3. URLSession
  4. Key-value observing
  5. Ad-hoc callbacks

Cocoa SDK ์ „๋ฐ˜์— ๊ฑธ์ณ์„œ ๋น„๋™๊ธฐ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ •๋ง ์ƒ๋‹นํžˆ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ด ๋ชจ๋“  ๊ฒƒ๋“ค์€ ๊ฐ๊ฐ ์ค‘์š”ํ•˜๊ณ  ๊ฐ๊ธฐ ๋‹ค๋ฅธ ์‚ฌ์šฉ๋ฒ•์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ชจ๋“  ๊ฒƒ๋“ค์„ ๋Œ€์ฒดํ•˜์ง€ ์•Š๊ณ , ๊ณตํ†ต์ ์„ ์ฐพ๊ธฐ ์œ„ํ•ด Combine์ด ๋“ฑ์žฅํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Combine

A unified, declarative API for processing values over time

  • Combine์€ ์‹œ๊ฐ„ ๊ฒฝ๊ณผ์— ๋”ฐ๋ฅธ ๊ฐ’ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ํ†ตํ•ฉ ์„ ์–ธ์  API์ž…๋‹ˆ๋‹ค.
  • Combine์€ ๊ธฐ์กด์˜ API๋ฅผ ๋Œ€์ฒดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Combine Features

  1. Generic
  2. Type Safe
  3. Composition first
  4. Request driven
  • Swift์šฉ์œผ๋กœ ๋งŒ๋“ค์–ด์ ธ์„œ Generic๊ณผ ๊ฐ™์€ Swift ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Type Safe ํ•˜๋ฏ€๋กœ ๋Ÿฐํƒ€์ž„์ด ์•„๋‹Œ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์˜ค๋ฅ˜๋ฅผ ์žก์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์กฐํ•ฉํ•˜์—ฌ ์‰ฝ๊ณ  ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์š”์ฒญ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ์•ฑ์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰๊ณผ ์„ฑ๋Šฅ์„ ์ž˜ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Combine์˜ ํ•ต์‹ฌ ๊ฐœ๋…

  1. Publisher
  2. Subscriber
  3. Operators

Publishers

  1. Defines how values and errors are produced
  2. Combine API์˜ ์„ ์–ธ์  ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. Publisher๋Š” ๊ฐ’๊ณผ ์˜ค๋ฅ˜๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๋ฐฉ์‹์„ ์ •์˜ํ•˜๋Š”๋ฐ์š”. ์‹ค์ œ๋กœ ๋ฐ˜๋“œ์‹œ ๊ทธ๊ฒƒ๋“ค์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ๊ณ , ์•„๋‹ ์ˆ˜๋„ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด์ฃ .
  3. Value type
  4. ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ’(Value) ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
  5. Allows registration of a Subscriber
  6. ๊ตฌ๋…์ž(Subscriber) ๋“ฑ๋ก๋„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
protocol Publisher {
	associatedtype Output
	associatedtype Failure: Error

	func subscribe<S: Subscriber>(_ subscriber: S)
		where S.Input == Output, S.Failure == Failure
}
  • Output
  • ์ƒ์„ฑํ•˜๋Š” ๊ฐ’์˜ ์ข…๋ฅ˜์ž…๋‹ˆ๋‹ค.
  • Failure
  • ์ƒ์„ฑํ•˜๋Š” ์˜ค๋ฅ˜์˜ ์ข…๋ฅ˜์ž…๋‹ˆ๋‹ค. ์—๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ Never ํƒ€์ž…(associated type)์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•ต์‹ฌ ๊ธฐ๋Šฅ (Subscribe: ๊ตฌ๋…)
  • Subscriber์˜ ์ž…๋ ฅ์€ Publisher์˜ ์ถœ๋ ฅ๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•˜๊ณ , Subscriber์˜ ์‹คํŒจ๋Š” Publisher์˜ ์ž…๋ ฅ๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

NotificationCenter

extension NotificationCenter {
	struct Publisher: Combine.Publisher {
		typealias Output = Notification
		typealias Failure = Never
		init(center: NotificationCenter, name: NotificationCenter.Name, object: Any? = nil)
	}
}

๋‹ค์Œ ์ฝ”๋“œ๋Š” NotificationCenter๋ฅผ ์œ„ํ•œ Publisher์ž…๋‹ˆ๋‹ค. Output์€ Notification, Failure๋Š” Never ํƒ€์ž…์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  center, name, object ์„ธ ๊ฐ€์ง€ ํ•ญ๋ชฉ์œผ๋กœ ์ดˆ๊ธฐํ™” ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ NotificationCenter๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋‹จ์ง€ ์ ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด์ฃ .

Subscribers

  1. Receives values and a completion
  2. Subscriber๋Š” Publisher์˜ ๋ฐ˜๋Œ€๋˜๋Š” ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. Publisher๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  Publiser๊ฐ€ ์œ ํ•œ(finite)ํ•œ ๊ฒฝ์šฐ ์™„๋ฃŒ๋ฅผ ํฌํ•จํ•œ ๊ฐ’์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  3. Reference type
  4. Subscriber๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐ’์„ ๋ฐ›์œผ๋ฉด ํ–‰๋™(์ฒ˜๋ฆฌ)ํ•˜๊ณ  ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ๋•Œ๋ฌธ์— class์™€ ๋™์ผํ•˜๊ฒŒ ์ฐธ์กฐ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
protocol Subscriber {
	associatedtype Input
	associatedtype Failure: Error

	func receive(subscription: Subscription)
	func receive(_ input: Input) -> Subscribers.Demand
	func receive(completion: Subscribers.Completion<Failure>)
}
  • Input
  • Failure
  • Subscriber ๊ฐ€ ์‹คํŒจ๋ฅผ ์ˆ˜์‹ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ Never ํƒ€์ž…(associated type)์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ•ต์‹ฌ ๊ธฐ๋Šฅ (Receive: ๊ตฌ๋…์„ ๋ฐ›๋Š” ๊ฒƒ)
  • Receive๋Š” Publiser์—์„œ Subsriber๋กœ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

Assign

extension Subscribers {
	class Assign<Root, Input>: Subscriber, Cancellable {
		typealias Failure = Never
		init(object: Root, keyPath: ReferenceWritableKeyPath<Root, Input>)
	}
}

Assign ํด๋ž˜์Šค๋Š” Input์„ ๋ฐ›์œผ๋ฉด ํ•ด๋‹น ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ์— ๊ธฐ๋ก์„ ํ•ฉ๋‹ˆ๋‹ค. Swift์—์„œ๋Š” ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์ž‘์„ฑํ•  ๋•Œ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•  ๋ฐฉ๋ฒ•์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— Assign์˜ Failure ์œ ํ˜•์€ Never๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

The Pattern

  1. ๋งŒ์•ฝ์— Subsriber๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Controller ๊ฐ์ฒด ๋˜๋Š” ๋‹ค๋ฅธ ํƒ€์ž…์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์€ Subsciber์™€ ํ•จ๊ป˜ Publisher์˜ subsribe ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์—ฐ๊ฒฐํ•ด์ฃผ์–ด์•ผ ํ•˜๋Š” ์ฑ…์ž„์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์ด ์‹œ์ ์—์„œ Publisher๋Š” Subsriber์—๊ฒŒ Subscription์„ ๋ณด๋‚ด, Subscriber๊ฐ€ Publisher์—๊ฒŒ ํŠน์ • ์ˆ˜์˜ ๊ฐ’ ๋˜๋Š” ๋ฌด์ œํ•œ ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  3. Publisher๋Š” ํ•ด๋‹น ์ˆ˜ ์ดํ•˜์˜ ๊ฐ’์„ Subscriber์—๊ฒŒ ์ž์œ ๋กญ๊ฒŒ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  4. Publisher๋Š” ์œ ํ•œํ•œ ๊ฒฝ์šฐ ๊ฒฐ๊ตญ Completion ๋˜๋Š” Error๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

์กธ์—…ํ•˜๋Š” ํ•™์ƒ๋“ค์— ๋Œ€ํ•œ ์•Œ๋ฆผ์„ ๋“ฃ๊ณ  ์กธ์—…ํ•˜๋ฉด, Wizard ๊ฐ์ฒด๋“ค์˜ ๊ฐ’์„ ์ผ๊ด„์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

  1. ์กธ์—…์— ๋Œ€ํ•œ ์•Œ๋ฆผ์€ NotificationCenter์˜ Publisher๋ฅผ ์ด์šฉํ•ด์„œ ์•Œ๋ฆฝ๋‹ˆ๋‹ค.
  2. Assign Subscriber๋ฅผ ๋งŒ๋“ค๊ณ  merlin์˜ ํ•™๋…„(grade)์„ ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  3. ๊ทธ ์ดํ›„์— ๊ตฌ๋…(subscribe)์„ ํ†ตํ•ด ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ์š”.

๊ทธ๋Ÿฐ๋ฐ ๋ญ”๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ์ปดํŒŒ์ผ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ํƒ€์ž…์ด ์ผ์น˜ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์œ„์—์„œ Publisher์˜ Output๊ณผ Subscriber์˜ Input์€ ํƒ€์ž…์ด ์ผ์น˜ํ•ด์•ผํ•œ๋‹ค๊ณ  ํ–ˆ์—ˆ์ฃ ? (๊ธฐ์–ต์•ˆ๋‚˜๋ฉด ์–ด์ฉ” ์ˆ˜ ์—†๊ตฌ์š”…)

Notification Center์—์„œ Notification์„ ๋ณด๋‚ด์ง€๋งŒ Assign์ด Int ํƒ€์ž…์— ๊ฐ’์„ ์“ฐ๊ณ  ์‹ถ์€ ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๊ทธ๋Ÿผ ์ง€๊ธˆ Int ํƒ€์ž…์ด ํ•„์š”ํ•œ๊ฑฐ์ฃ . ๊ทธ๋ž˜์„œ ์ด ์‚ฌ์ด์— ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•œ๋ฐ์š”. ์ด๋ฅผ ์œ„ํ•ด ํ•„์š”ํ•œ ๊ฒƒ์ด Operator์ž…๋‹ˆ๋‹ค.

Operator

  1. Publisher ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•ฉ๋‹ˆ๋‹ค. ์ฑ„ํƒํ•  ๋•Œ๊นŒ์ง€๋Š” Publisher์ธ ๊ฒƒ์ด์ฃ .
  2. ์„ ์–ธ์ ์ด๋ฏ€๋กœ ๊ฐ’ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
  3. ๊ฐ’ ๋ณ€๊ฒฝ, ์ถ”๊ฐ€, ์ œ๊ฑฐ ๋˜๋Š” ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  4. upstream์ด๋ผ๊ณ  ๋ถˆ๋ฆฌ์šฐ๋Š” ๋‹ค๋ฅธ Publisher๋ฅผ ๊ตฌ๋…ํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ downstream์ด๋ผ๊ณ  ๋ถ€๋ฅด๋Š” Subscriber์—๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

Map

extension Publishers {
	struct Map<Upstream: Publisher, Output>: Publisher {
		typealias Failure = Upstream.Failure
		
		let upstream: Upstream
		let transform: (Upstream.Output) -> Output
	}
}

Map์ด๋ผ๋Š” Operator๋„ ๊ฒฐ๊ตญ Publisher์˜€๋„ค์š”? Map์€ ์–ด๋–ค Upstream์— ์—ฐ๊ฒฐํ•˜๊ณ  Upstream์˜ Output์„ ์ž์ฒด Output์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜๋Š” ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ž์ฒด์ ์œผ๋กœ Failure๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ Upstream์˜ Failure ํƒ€์ž…์„ mirroringํ•˜๊ณ  ๊ทธ๋ƒฅ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.


let converter = Publishers.Map(upstream: graduationPublisher) { note in 
	return note.userInfo?["NewGrade"] as? Int ?? 0
}

์ด ์ฝ”๋“œ๋งŒ ์ž ๊น ์ดํ•ดํ•˜๊ณ  ๋„˜์–ด๊ฐ€๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์š”. (์ฐธ ํฅ๋ฏธ๋กญ๋„ค์š”..ใ…Ž) ๊ธฐ์กด์— ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋˜ ์ด์œ ๋Š” Publisher์™€ Subscriber์˜ ํƒ€์ž…์ด ๋งž์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ์šฐ๋ฆฌ๋Š” ์ค‘๊ฐ„ ๊ณผ์ •์ด ํ•„์š”ํ–ˆ๊ณ , ๊ทธ ์—ญํ• ์„ ๋„์™€์ฃผ๋Š” Operator๊ฐ€ ๋“ฑ์žฅํ•œ ๊ฒƒ์ด์ฃ . Operator ์—ญ์‹œ ๊ฒฐ๊ตญ ์ด์ „(upstream)์˜ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์™€์„œ ๋ณ€๊ฒฝํ•˜๊ณ  ๋˜ ๋‹ค๋ฅธ Output์„ ๋‚ด๋ณด๋‚ด๋Š” Publisher๋ผ๊ณ  ๋ด๋„ ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ž ๋‹ค์‹œ ๋Œ์•„์™€์„œ ์ง€๊ธˆ ์šฐ๋ฆฌ๋Š” Notification์„ Int๋กœ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์ด์ž–์•„์š”? graduationPublisher์„ upstream์œผ๋กœ ๊ฐ€์ ธ์™€์„œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. graduationPublisher์˜ Output์ธ Notification์˜ userInfo Int ๊ฐ’์„ ๋‹ค์‹œ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋ฉด ๋  ๊ฒƒ ๊ฐ™์•„์š”. ๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ์ •์ƒ์ ์œผ๋กœ subscribe๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

extension Publisher {
	func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Self, T> {
		return Publishers.Map(upstream: self, transform: transform)
	}
}

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ๋ฌธ์„ ์ข€ ๋” ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ extension์„ ์ด์šฉํ•˜๋ฉด ๋ชจ๋“  Publisher๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์ฃ .

let cancellable = NotificationCenter.default.publisher(for: .graduated, object: merlin)
	.map { note in return note.userInfo?["NewGrade"] as? Int ?? 0  }
	.assign(to: \.grade, on: merlin)

์ด ๊ตฌ๋ฌธ์„ ๋ณด๋ฉด ๋‹จ๊ณ„๋ณ„๋กœ ๋งค์šฐ ์„ ํ˜•์ ์ด๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ํ๋ฆ„์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Assign์€ cancelable ํ•ญ๋ชฉ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. Cancelation ๋˜ํ•œ Combine์— ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Cancelation์„ ํ†ตํ•ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ Publisher ๋ฐ Subscriber๋ฅผ ์กฐ๊ธฐ์— ํ•ด์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋‹จ๊ณ„๋ณ„ ๊ตฌ๋ฌธ์€ Combine ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์˜ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ๊ฐ ๋‹จ๊ณ„๋Š” ์ฒด์ธ์˜ ๋‹ค์Œ ๋ช…๋ น์–ด ์„ธํŠธ๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ Publisher์—์„œ ์ผ๋ จ์˜ Operator๋ฅผ ๊ฑฐ์ณ Subscriber๋กœ ๋๋‚˜๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฌํ•œ Operator๋ฅผ ๋งŽ์ด ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด๋ฅผ Declarative Operator API๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

Declarative Operator API

  1. Functional transformations
  2. Map, Filter, Reduce ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  3. List operations
  4. Publisher์˜ ์ฒซ ๋ฒˆ์งธ, ๋‘ ๋ฒˆ์งธ ๋˜๋Š” ๋‹ค์„ฏ ๋ฒˆ์งธ element๋ฅผ ์ทจํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค.
  5. Error handling
  6. ์˜ค๋ฅ˜๋ฅผ ๊ธฐ๋ณธ๊ฐ’ ๋˜๋Š” ๋Œ€์ฒด๊ฐ’์œผ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ž‘์—…๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  7. Thread or queue management
  8. ์Šค๋ ˆ๋“œ, ํ์—์„œ ์ด๋™, ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ ์ด๋™, UI ์ž‘์—…์„ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋กœ ์ด๋™ From ๋ฃจํ”„, ๋””์ŠคํŒจ์น˜ ํ, ํƒ€์ด๋จธ ์ง€์›, ํƒ€์ž„์•„์›ƒ ๋“ฑ๊ณผ ๊ฐ™์€ ์ž‘์—… ์—ญ์‹œ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  9. Scheduling and time

์‚ฌ์‹ค Operator๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์„œ ์—ฐ์‚ฐ์ž๋ฅผ ์ฐพ๊ณ  ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋ถ€๋‹ด์„ ๋Š๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Try composition first

Apple์—์„œ ๊ถŒ์žฅํ•˜๋Š” ๊ฒƒ์€ Combine์— ๋Œ€ํ•œ ํ•ต์‹ฌ ๋””์ž์ธ ์›์น™์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฐ”๋กœ Composition์ธ๋ฐ์š”. ์• ์ดˆ์— ์„ค๊ณ„๋ถ€ํ„ฐ ๋งŽ์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ Operator๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋Œ€์‹ , ์•ฝ๊ฐ„์˜ ํ•ต์‹ฌ ์ž‘์—…๋งŒ ์ˆ˜ํ–‰ํ•˜๋Š” Operator๋ฅผ ๋งŽ์ด ์ œ๊ณตํ•˜์—ฌ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋“ค์ด ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก Swift Collection API์—์„œ ์ด๋ฆ„์„ ๋งŽ์ด ๋”ฐ์™”๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์™ผ์ชฝ์€ ๋™๊ธฐ API์ด๊ณ , ์˜ค๋ฅธ์ชฝ์—๋Š” ๋น„๋™๊ธฐ API์ž…๋‹ˆ๋‹ค. ์œ„์ชฝ์€ ๋‹จ์ผ ๊ฐ’, ์•„๋ž˜์ชฝ์€ ๋‹ค์ˆ˜์˜ ๊ฐ’์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ๋‹จ์ผ ๊ฐ’์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋‚˜ํƒ€๋‚ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ Future๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋‹ค์ˆ˜์˜ ๊ฐ’์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋‚˜ํƒ€๋‚ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ Publisher๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Operator Example

์ด ์˜ˆ์‹œ๋Š” ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ ์ •์ˆ˜๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ 0์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ž˜๋ชป๋œ ๊ฐ’์ด ์ €์žฅ๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค nil์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์•„๋ณด์ž…๋‹ˆ๋‹ค.

compactMap

์ด ๋•Œ, Swift 4.1์— ๋„์ž…๋œ compactMap์„ ์ด์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. compactMap๋„ ์—ญ์‹œ Combine์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌ๋ฉด ์ด์ „๊ณผ ๋‹ค๋ฅด๊ฒŒ ํด๋กœ์ €์—์„œ nil์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด compactMap์€ ์ด๋ฅผ ํ•„ํ„ฐ๋งํ•˜์—ฌ downstream์œผ๋กœ ๋” ์ด์ƒ ์ง„ํ–‰๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

filter

์ด๋ฒˆ์—๋Š” filter operator๋ฅผ ์ด์šฉํ•ด์„œ 5ํ•™๋…„ ์ด์ƒ๋งŒ ๊ฑธ๋Ÿฌ์ง€๋„๋ก ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Array์—์„œ ์‚ฌ์šฉ๋˜๋˜ filter์™€ ๋™์ž‘์ด ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

prefix

๋˜๋Š” ๋ฐ˜๋ณต๋˜๋Š” ์ž‘์—… ๋™์•ˆ 3๋ฒˆ๊นŒ์ง€๋งŒ ๊ฐ’์„ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ์œผ๋ฉด prefix operator๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

Combining Publishers

2๊ฐ€์ง€ Operator๋ฅผ ๋” ์†Œ๊ฐœํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Zip

๋งˆ๋ฒ•์‚ฌ ์•ฑ์—์„œ ์ง€ํŒก์ด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค. ์ง€ํŒก์ด๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” 3๊ฐ€์ง€ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๊ณ„์†ํ•˜๋„๋ก ํ—ˆ์šฉํ•˜๋ ค๋ฉด ์œ„์˜ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์–ด์•ผ ํ•˜๊ฒ ์ฃ . zip์„ ์ด์šฉํ•˜๋ฉด 3๊ฐœ์˜ ์ž‘์—…์ด ๋ชจ๋‘ ์™„๋ฃŒ๋œ ์‹œ์ ์„ ์บ์น˜ํ•ด์„œ Continue ๋ฒ„ํŠผ์„ ํ™œ์„ฑํ™” ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Zip์€ ์—ฌ๋Ÿฌ Upstream์„ ๋‹จ์ผ Tuple๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. downstream์œผ๋กœ ๊ฐ’์„ ์ „๋‹ฌํ•˜๊ณ  ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋ ค๋ฉด ๋ชจ๋“  upstream์—์„œ ์ž…๋ ฅ(Input)์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ Publisher์—์„œ A๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ Publisher์—์„œ 1์„ ์ƒ์„ฑํ•˜๋ฉด ์ด์ œ Tuple์„ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ๊ฐ’์„ Subscriber์—๊ฒŒ downstream์œผ๋กœ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์•ฑ์—์„œ๋Š” Bool ํƒ€์ž…์„ ์ œ๊ณตํ•˜๋Š” 3๊ฐœ์˜ ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š”๋ฐ 3๊ฐœ์˜ upstream์ด ํ•„์š”ํ•œ Zip ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Tuple์„ ๋‹จ์ผ Bool๋กœ ๋งคํ•‘ํ•˜๊ณ  ์—ฌ๊ธฐ์—์„œ ๋ฒ„ํŠผ์˜ isEnabled ์†์„ฑ์„ ๋ฐ”๊ฟ”์ค๋‹ˆ๋‹ค. 3๊ฐœ์˜ ์ž‘์—…์ด ๋ชจ๋‘ ์™„๋ฃŒ๋˜๋ฉด true๊ฐ’์ด ์„ธํŒ…๋˜๊ณ  ๋ฒ„ํŠผ์ด ํ™œ์„ฑํ™” ๋ฉ๋‹ˆ๋‹ค.

CombineLatest

Play ๋ฒ„ํŠผ์ด ํ™œ์„ฑํ™”๋˜๊ธฐ ์ „์— 3๊ฐœ์˜ ์Šค์œ„์น˜๋ฅผ ๋ชจ๋‘ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•˜๊ณ , ํ•˜๋‚˜๋ผ๋„ ๋น„ํ™œ์„ฑํ™”๋œ๋‹ค๋ฉด Play ๋ฒ„ํŠผ๋„ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿด ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด CombineLatest์ž…๋‹ˆ๋‹ค.

Zip๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์—ฌ๋Ÿฌ upstream์˜ Input์„ ๋‹จ์ผ ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ upstream์—์„œ ๋ฐ›์€ ๋งˆ์ง€๋ง‰ ๊ฐ’์„ ์ €์žฅํ•˜๊ณ  ์ด๋ฅผ ๋‹จ์ผ downstream ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ Publisher๊ฐ€ A๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋‘ ๋ฒˆ์งธ Publisher๊ฐ€ A1์„ ์ƒ์„ฑํ•˜๋ฉด ์ด๋ฅผ ๋ฌธ์ž์—ดํ™”ํ•˜๊ณ  downstream์œผ๋กœ ๋‚ด๋ ค๋ณด๋‚ด๊ณ  ๋‚˜์ค‘์— ๋‘ ๋ฒˆ์งธ Publisher๊ฐ€ ์ƒˆ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋ฉด ์ฒซ ๋ฒˆ์งธ Publisher์˜ ์ด์ „ ๊ฐ’๊ณผ ๊ฒฐํ•ฉํ•˜์—ฌ ์ƒˆ ๊ฐ’์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

upstream์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.


Combine์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ชจ๋“  ๊ฒƒ์„ ๋ณ€ํ™˜ํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ๋ช‡ ๊ฐ€์ง€๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์„ธ์š”.

  1. NotificationCenter์˜ ์•Œ๋ฆผ์„ ์ˆ˜์‹ ํ•˜๊ณ  ์กฐ์น˜ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๋ ค๋ฉด filter๋ฅผ ์ด์šฉํ•ด๋ณด์„ธ์š”.
  2. ์—ฌ๋Ÿฌ ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๊ฒฐ๊ณผ์— ๊ฐ€์ค‘์น˜๋ฅผ ๋ถ€์—ฌํ•˜๋ฉด ๋„คํŠธ์›Œํฌ ์ž‘์—…์„ ํฌํ•จํ•˜์—ฌ Zip์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. URLSession์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€ JSON Decoder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ํŠน์ • ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ์—๋„ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. decode operator๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”.