Combine์ด ๋ฌด์—‡์ผ๊นŒ? Apple์ด ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์„ ๋“ค์–ด๋ณด์ž.

What is Combine

์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์—ฐ์‚ฐ์ž๋ฅผ ๊ฒฐํ—™ํ•˜์—ฌ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

์ผ๋‹จ ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ๊ฐ€ ๋ฌด์—‡์ด ์žˆ๋Š”์ง€๋ถ€ํ„ฐ ์•Œ์•„๋ณด์ž. ์ด๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด์„œ WWDC์—์„œ๋Š” Create Account ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ์˜ˆ์‹œ๋กœ ํ•˜์—ฌ ์„ค๋ช…์„ ์ด์–ด๊ฐ„๋‹ค.

์š”๊ตฌ์‚ฌํ•ญ์€ ์œ„์™€ ๊ฐ™๋‹ค. username ์œ ํšจ์„ฑ ํŒ๋‹จ, password matching ํŒ๋‹จ, ๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ๋“ค์ด ๋งž์„ ๊ฒฝ์šฐ, ๋ฒ„ํŠผ์ด ํ™œ์„ฑํ™”๋˜๋Š” ๊ฒƒ, ์ด ์„ธ๊ฐ€์ง€ ์ด๋‹ค.

์ด๊ฑธ ๊ฐ€๋Šฅ์ผ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ €, interaction ์—ฌ๋ถ€๋ฅผ ์ „๋‹ฌํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. ์ด ๋ถ€๋ถ„์—์„œ Target/Action ๋””์ž์ธ ํŒจํ„ด์ด ์‚ฌ์šฉ๋œ๋‹ค. ์œ ์ €๊ฐ€ TextField์— ์ž…๋ ฅํ•œ ์ดํ›„, ์ฆ‰, ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„ ํ†ต์‹ ์„ ํ†ตํ•ด ๊ฒ€์ฆ์„ ํ•œ๋‹ค๋ฉด network resource ๋‚ญ๋น„์ผ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ํŠน์ • ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์„ ๋‘๊ณ  ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ํ•ด์•ผ ํ•œ๋‹ค. ์ด ๋ถ€๋ถ„์—์„œ๋Š” Timer๊ฐ€ ์‚ฌ์šฉ๋œ๋‹ค. ๋„คํŠธ์›Œํฌ Progress update๋ฅผ ์œ„ํ•ด์„œ๋Š” KVO๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ์ƒ๊ฐ์ด๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ TextField์— ๊ฐ’์„ ์ž…๋ ฅํ•˜๋ฉด, URLSession์„ ํ†ตํ•ด ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ์ด 3๊ฐœ์˜ TextField์— ์ž…๋ ฅ๊ฐ’์ด ๋ชจ๋‘ ๋‹ด๊ฒจ์กŒ๋‹ค๋ฉด, ์ด ๋ชจ๋“  ๊ฐ’์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Merge๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋ชจ๋‘ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ–ˆ๋‹ค๋ฉด, ํ•˜๋‹จ์˜ Create Account Button์˜ Enable ์—ฌ๋ถ€๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ Cocoa API์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋น„๋™๊ธฐ API๋“ค์„ ๋งŒ๋‚˜๊ฒŒ ๋œ๋‹ค.

์ด๋ ‡๊ฒŒ ๋‹ค์–‘ํ•œ ์š”์†Œ๋“ค์ด ์žˆ๋Š”๋ฐ, ์ด ๋…€์„๋“ค์€ ๊ฐ๊ฐ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๋‹ค๋ฅด๋‹ค. ๊ทธ๋ž˜์„œ ์ด๊ฒƒ๋“ค์„ ์—ฎ์–ด์„œ ์“ฐ๋ ค๋‹ˆ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•˜๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์— ์ฐฉ์•ˆํ•˜์—ฌ Apple์€ ์ด ๋…€์„๋“ค์˜ ๊ณตํ†ต์ ์„ ์ถ”์ถœํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ๊ทธ๊ฒƒ์ด Combine์ด๋‹ค.

Combine

A unified, declarative API for processing values over time.

์‹œ๊ฐ„์— ํ๋ฆ„์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌํ•˜๋Š” โ€œ์„ ์–ธ์ โ€ API์ด๋‹ค. ํŠน์ง•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • Generic: Genericํ•˜๊ฒŒ ์ž‘์„ฑ ํ›„, ์ด๊ณณ ์ €๊ณณ์—์„œ ํ™œ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • Type Safe: compile time์— Error๋ฅผ ์žก์„ ์ˆ˜ ์žˆ๋‹ค.
  • Composition first: functional programming์˜ ํ•จ์ˆ˜ ์กฐํ•ฉ ๊ฐœ๋…์„ ์ฐจ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ operator๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์–ป๋Š” ๊ฒƒ์— ์ง‘์ค‘ํ–ˆ๋‹ค.
  • Request driven: ์š”์ฒญ์‹œ์— ์ฒ˜๋ฆฌ๋œ๋‹ค. ํ•„์š”ํ•  ๋•Œ๋งŒ ์ฒ˜๋ฆฌ๋˜๋‹ˆ Resource๋ฅผ ์•„๋‚„ ์ˆ˜ ์žˆ๋‹ค.

Publishers

์–ด๋–ป๊ฒŒ Value์™€ Error๊ฐ€ ์ƒ์‚ฐ๋  ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•ด ์ •์˜ํ•œ๋‹ค. ๊ฐ’ํƒ€์ž…, ์ฆ‰ struct๋กœ ์„ ์–ธ๋˜์–ด ์žˆ์œผ๋ฉฐ, Subscriber๋“ค์˜ ๋“ฑ๋ก์„ ๋ฐ›๋Š” ๋…€์„์ด๋‹ค.

  • Publish Values and Error
  • Value Type: struct
protocol Publisher {
    associatedtype Output
    associatedtype Failure: Error
 
    func subscribe<S: Subscriber>(_ subscriber: S)
        where S.Input == Output, S.Failure == Failure
}

๋‘๊ฐœ์˜ ์—ฐ๊ด€๊ฐ’์ด ์žˆ๋Š”๋ฐ, Output, Failure์ด๋‹ค. ์„ฑ๊ณต์‹œ์— ๊ฐ’์„ ์ฃผ๋Š” ๋…€์„์ด Output์ด๊ณ  ์‹คํŒจํ•  ์‹œ ์ฃผ๋Š” Error๊ฐ€ Failure์ด๋‹ค. ๋งŒ์•ฝ Error๋ฅผ ์ค„ ์ˆ˜ ์—†์„ ๊ฒฝ์šฐ Never๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Publisher๋Š” ๋”ฑ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋ฐ, subscrive()์ด๋‹ค. ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ œ์•ฝ์ด ํ•„์š”ํ•œ๋ฐ, publisher์˜ ๊ตฌ๋…์„ ๋ฐ›๋Š” subscriber์˜ input๊ณผ Failure type์ด ๊ฐ™์•„์•ผ ํ•œ๋‹ค. ์ด ๋ถ€๋ถ„์€ ์‚ฌ์‹ค ๋‹น์—ฐํ•˜๋‹ค.

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

Notification Center์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์œ„์™€ ๊ฐ™์€ extension์ด ์ถ”๊ฐ€๋˜์–ด ์žˆ๋‹ค. ์‹ค์ œ๋กœ struct type์ด๋ฉฐ, output type์€ Notification, Failure type์€ Never์ด๋‹ค.

Subscribers

Subscriber๋Š” Publisher์™€ ๋Œ€์ฒ™์ ์— ์žˆ๋Š” ๋…€์„์ด๋‹ค. Publisher๊ฐ€ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๊ฐ’์„ ๋ฐ›๊ณ , Publisher๊ฐ€ ๋งŒ์•ฝ ์œ ํ•œํ•˜๋‹ค๋ฉด, (์ฆ‰, ๊ฐ’ ๋ช‡๊ฐœ๋ฅผ ๋‚ด๋ณด๋‚ด๊ณ  ์‚ฌ์šฉ๊ฐ€์น˜๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ๋…€์„๋“ค์„ ๋งํ•จ) ๋๋‚ฌ์„ ๋•Œ ํ•˜๋Š” ํ–‰๋™์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋…€์„์ด๋‹ค. Reference Type์ด๋‹ค. ์ฆ‰ Class๋ผ๋Š” ๋ง์ด๋‹ค.

  • Reference Type: Class
  • Receive Values and Completion: ๊ฐ’์„ ๋ฐ›๊ณ , ๋๋‚ฌ์„ ๋•Œ ๋™์ž‘ ์ •์˜
protocol Subscriber {
    associatedtype Input
    associatedtype Failure: Error
 
    func receive(subscription: Subscription)
    func receive(_ input: Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Failure>)
}

์—ฐ๊ด€๊ฐ’ 2๊ฐœ, Input, Failure๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๊นŒ ๋งํ–ˆ๋“ฏ Publisher์™€ Type์ด ๊ฐ™์•„์•ผ ๋“ฑ๋ก์ด ๊ฐ€๋Šฅํ•˜๋‹ค. Error Type์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Never๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ถ”๊ฐ€์ ์œผ๋กœ 3๊ฐœ์˜ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ๋จผ์ €, Subscription์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š”๋ฐ, Subscription์€ ์–ด๋–ป๊ฒŒ Subscriber๊ฐ€ Publihser๋กœ ๋ฐœ์ƒ๋œ data๋ฅผ Subscriber๋กœ ์ค„ ์ˆ˜ ์žˆ๋Š”์ง€์— ๋Œ€ํ•œ ๊ฒƒ์ด๋‹ค. (?)

๋‘๋ฒˆ์งธ๋กœ๋Š” Input์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋งˆ์ง€๋ง‰์œผ๋กœ๋Š” finiteํ•œ Publihser์— ์—ฐ๊ฒฐ๋œ ๊ฒฝ์šฐ completion์„ ๋ฐ›๋Š”๋ฐ, Finished๊ฑฐ๋‚˜ Failure์ผ ๋•Œ์˜ ๋™์ž‘์„ ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

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

์ด Subscriber protocol์„ ์ฑ„ํƒํ•˜์—ฌ ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” ๋…€์„๋“ค์€ Combine์•ˆ์˜ Subscrivers๋ผ๋Š” enum์— ์ •์˜๋˜์–ด ์žˆ๋‹ค. WWDC์—์„œ๋Š” ๊ทธ ์˜ˆ์ธ Assign์„ ๋“ค๊ณ  ์™”๋‹ค. Root๋ผ๋Š” Type์€ Keypath์— ์ •์˜๋˜์–ด ์žˆ๋Š” ํƒ€์ž…์ด๋‹ค. KeyPath๋ฅผ ์ฐธ๊ณ ํ•˜์ž.

์ด์ œ ์ด๋…€์„์„ ๋ณด๋ฉด, Input Type์œผ๋กœ ๊ฐ’์„ ๋ฐ›์•„์„œ object๋กœ ์„ ์–ธ๋˜์–ด ์žˆ๋Š” Root Object์— ๊ทธ ๊ฐ’์„ keypath๋ฅผ ํ†ตํ•ด ์ฐพ์•„ ์ ์šฉํ•˜๋Š” ์—ญํ• ์„ ํ•˜๊ณ  ์žˆ๋‹ค. ๋‹จ์ˆœํžˆ ๊ฐ’์„ ์“ฐ๋Š” ํ–‰์œ„๋ฅผ ํ•˜๊ณ  ์žˆ๊ณ , Swift๋Š” ์ด์— ํ•ด๋‹น๋˜๋Š” Error๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— Failure๋Š” Never ํƒ€์ž…์ด๋‹ค.

The Pattern

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋…€์„๋“ค์„ ์‹ค์ œ๋กœ๋Š” ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ? ํ•œ๋ฒˆ์˜ ์ถœ๊ฐ„๊ณผ ๊ตฌ๋…์ด ์ด๋ฃจ์–ด์ง€๋Š” ๊ณผ์ •์„ ํ•œ๋ฒˆ ์•Œ์•„๋ณด์ž.

๋จผ์ € Subscriber๋Š” Publisher์— Attach๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์ƒ์„ฑ๋œ Subscriber๋ฅผ ์ธ์ž๋กœ ๋„ฃ์–ด์„œ Publisher์—๊ฒŒ ์ฃผ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ด ์š”์ฒญ์„ ๋ฐ›๊ฒŒ๋˜๋ฉด Publisher๋Š” Subscription์ด๋ผ๋Š” ๊ฐ์ฒด instance๋ฅผ ์ธ์ž๋กœ ๋‹ด์•„์„œ ์ฃผ๊ฒŒ๋œ๋‹ค.(์ฆ‰, 1์—์„œ ๋ฐ›๋Š” Subscriber instance์˜ receive(subscription:) ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ) ์ด Subscription ๊ฐ์ฒด๋Š” Subscirber๊ฐ€ Publisher๋กœ๋ถ€ํ„ฐ ์›ํ•˜๋Š” ๊ฐ’์„ ์š”๊ตฌํ•˜๊ฑฐ๋‚˜, ๊ตฌ๋…์„ ์ทจ์†Œํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•œ๋‹ค.

์„ธํŒ…์ด ์™„๋ฃŒ๋˜๋ฉด Subscriber๋Š” Publisher์—๊ฒŒ ์›ํ•˜๋Š” ๊ฐœ์ˆ˜(๋ฌด์ œํ•œ๋„ ์žˆ์Œ)๋งŒํผ์˜ request๋ฅผ ํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ ์ˆœ๊ฐ„ ๋ถ€ํ„ฐ Publisher๋Š” ์š”์ฒญํ•œ ๊ฐœ์ˆ˜๋งŒํผ์˜ ๊ฐ’, ํ˜น์€ ๊ทธ๋ณด๋‹ค ์ ์€ ๊ฐœ์ˆ˜๋ฅผ ๋ณด๋‚ธ๋‹ค. ๋งŒ์•ฝ์— Publisher๊ฐ€ finiteํ•˜๋‹ค๋ฉด, ์ตœ์ข…์ ์œผ๋กœ subscriber์˜ receive(completion:) ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํ†ต์‹ ์ด ์ข…๋ฃŒ๋œ๋‹ค.

Comeback to Wizard

// Using Publisher and Subscriber
class Wizard {
    var grade: Int
}
 
let merlin = Wizard(grade: 5) // ๋‚ด๊ฐ€ ์ถ”์ ํ•˜๊ณ  ์‹ถ์€ Object๋ฅผ ๋งŒ๋“ค๊ธฐ
let graduationPublisher = NotificationCenter.Publisher(center: .default, // Notification Center์˜ Predefined Publisher๋ฅผ ํ™œ์šฉํ•ด์„œ ์•Œ๋ฆผ ๋ฐ›๊ธฐ
                                                       name: .graduated, 
                                                       object: merlin)
 
 
let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade) // ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ ๋ฐ›์•„์„œ merlin๊ฐ์ฒด์˜ .grade property์— ๋ฐ˜์˜ํ•œ๋‹ค.
 
graduationPublisher.subscribe(gradeSubscriber) // NOT WORKING!

Wizard์— ๊ด€๋ จ๋œ ์•ฑ์„ ๋งŒ๋“ค๊ณ  ์žˆ์—ˆ๋˜ ๊ฒƒ์„ ๋– ์˜ฌ๋ ค๋ณด์ž. Wizard๋Š” ํ•™๋…„์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ํ•™๋…„์ด ๋†’์•„์ง์— ๋”ฐ๋ผ ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด ์ค„๊ฒƒ์ด๋‹ค. Notification Center๊ฐ€ ์ƒˆ๋กœ์šด ๊ฐ’์„ userInfo์— ๋„ฃ์–ด์ค„ ๊ฒƒ์ด๊ณ , ์ด๋ฅผ ๋ฐ˜์˜ํ•ด์ฃผ๋Š” ์ฝ”๋“œ๋ฅผ ์งœ๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

์ผ๋‹จ์€, ๋‚ด๊ฐ€ ์ถ”์ ํ•˜๊ณ  ์‹ถ์€ Object๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ๊ณ , Notification Center์˜ Predefined Publisher๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ณ€๊ฒฝ๋œ ์•Œ๋ฆผ์„ ๋ฐ›์•„๋ณด์ž. object๋Š” merlin์œผ๋กœ ์ง€์ •ํ•˜์—ฌ, ํ•ด๋‹น notification์„ ์ „์†กํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ช…์‹œํ•ด์ฃผ์ž.

Subscriber๋Š” ๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ฐ’์„ ๋ฐ›์•„์„œ ๋ฐ˜์˜ํ•ด์ฃผ์–ด์•ผ ํ•˜๋‹ˆ, Assign Subscriber๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ๊ณ , ๋ฐ˜์˜ํ•˜๊ณ  ์‹ถ์€ ๊ฐ์ฒด์™€ keypath๋ฅผ ์ธ์ž๋กœ ๋„ฃ์–ด์ฃผ์ž.

์ด๋ ‡๊ฒŒ ํ•œ๋’ค subscribe๋ฅผ ํ•˜๋ฉด ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ด๋Š” ๋‹น์—ฐํ•˜๋‹ค. ์•ž์—์„œ Publisher์˜ Output, Failure Type๊ณผ Subscriber์˜ Input, Failure Type์ด ๋งž์•„์•ผ ํ•œ๋‹ค๊ณ  ํ–ˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์•ž์—์„œ Notification Center์˜ predefined Publisher์˜ Output Type์„ ๋ดค์—ˆ๋Š”๋ฐ, Notification์ด์—ˆ๋‹ค. ๋ฐฉ๊ธˆ ๋งŒ๋“  Subscriber์˜ ๊ฒฝ์šฐ Input Type์ด Int์ด๋‹ค. KeyPath๊ฐ€ <Root(Wizard), Int>๋กœ ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. (์•ž์—์„œ Assign<Root, Input> ์ด์—ˆ๋Š”๋ฐ, KeyPath์˜ ๋‘๋ฒˆ์งธ Type๊ณผ ๊ฐ™์•˜๋‹ค) ๊ฒฐ๊ตญ Type์ด ๋งž์ง€ ์•Š์•„ Compile์‹œ ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค.

Operators

์ด๋Ÿฐ ์ƒํ™ฉ์ด๋ผ๋ฉด ์šฐ๋ฆฌ๋Š” Publisher์™€ Subscriber ์‚ฌ์ด์— ๋ฌด์–ธ๊ฐ€ ๋ณ€๊ฒฝ์‹œ์ผœ์ค„ ๊ฒƒ์ด ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋œ๋‹ค. ๊ทธ๊ฒŒ Operator์ด๋‹ค.

Operator์˜ ํŠน์ง•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • Publisher๋ฅผ ์ฑ„ํƒ: ํ•˜์œ„๋กœ ๊ฐ’์„ ๋‹ค์‹œ ๋ณด๋‚ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ
  • ๊ฐ’์˜ ๋ณ€ํ™”๋ฅผ ์œ„ํ•œ ํ–‰์œ„๋ฅผ ๊ธฐ์ˆ 
  • Upstream์œผ๋กœ Publisher๋ฅผ Subscribe
  • Downstream์œผ๋กœ Subscriber์—๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌ
  • Value Type: struct
extension Publishers {
    struct Map<Upstream: Publisher, Output>: Publisher {
    typealias Failure = Upstream.Failure
 
    let upstream: Upstream
    let transform: (Upstream.Output) -> Output
    } 
}

์‹ค์ œ๋กœ Operator๋ฅผ ๋ณด๋ฉด, Publishers ์•„๋ž˜์— ์ •์˜๋˜์–ด ์žˆ๋‹ค. Publishers๋Š” Subscribers์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Apple์—์„œ ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š” ๋…€์„๋“ค์„ ๋งŒ๋“ค์–ด ๋‘” Enumeration Type์ด๋‹ค. Upstream์œผ๋กœ Publisher Type์„ ๋ฐ›๊ณ , Output Type์„ ๊ฐ–๋Š”๋ฐ. ์ด ๋•Œ์˜ ์ œ์•ฝ์€ ์•„๋ฌด๊ฒƒ๋„ ์—†๋‹ค. Publisher๋ฅผ returnํ•ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค๋Š” ๋ง์ด๋‹ค. ๋‹จ, ์—ฌ๊ธฐ์„œ Failure๋Š” upstream์˜ Failure type๊ณผ ๋™์ผํ•œ Type์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

// Using Publisher and Subscriber
class Wizard {
    var grade: Int
}
 
let merlin = Wizard(grade: 5)
let graduationPublisher = NotificationCenter.Publisher(center: .default, 
                                                       name: .graduated, 
                                                       object: merlin)
 
let converter = Publishers.Map(upstream: graduationPublisher) { note in
return note.userInfo?["NewGrade"] as? Int ?? 0
}
 
let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade)
 
converter.subscribe(gradeSubscriber) // WORKING!

์ด์ œ ์œ„์˜ ์•ˆ๋๋˜ ๋…€์„์„ ์†๋ณด๋ฉด ์ด๋ ‡๊ฒŒ ๋œ๋‹ค. Publisher๋ฅผ ๋ฐ›์•„์„œ, ๊ฐ’์„ ๋ณ€ํ˜•ํ•ด ์ค€ ๋’ค, ์›ํ•˜๋Š” Output์„ ๋‚ด๋ฑ‰๋Š”๋‹ค. subscriber๋Š” graduationPublisher๊ฐ€ ์•„๋‹Œ converter์˜ Publisher์— ์—ฐ๊ฒฐํ•ด์ฃผ์–ด์•ผ ์›ํ•˜๋Š” ๊ฐ’์„ ๋ฐ›์•„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

Operator Construction

ํ•˜์ง€๋งŒ ์ €๋ ‡๊ฒŒ ์“ฐ๋ผ ๊ทธ๋Ÿฌ๋ฉด ์•„๋ฌด๋„ ์•ˆ์“ธ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ Apple์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Publisher์— Operator๋“ค์„ ๋งŒ๋“ค์–ด ๋‘์—ˆ๋‹ค.

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

Publisher ์— extension์œผ๋กœ map์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด๋‘์–ด, Output์„ ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝํ•  ์ง€์— ๋Œ€ํ•ด์„œ๋งŒ ์ž‘์„ฑํ•˜๋ฉด, Publisher.Map Operator๋ฅผ ๋งŒ๋“ค์–ด return ํ•ด์ค€๋‹ค.

๋˜ํ•œ Subscriber๋„ ๊ฐ„๋‹จํ•œ ๊ฒฝ์šฐ์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Publisher ํƒ€์ž…์— extension์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค.

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

๊ทธ๋ž˜์„œ ๊ฒฐ๊ณผ์ ์œผ๋กœ ์šฐ๋ฆฌ๊ฐ€ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ์ด๋Ÿฐ ๋ชจ์–‘์ด ๋‚˜์˜ค๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด์ œ ๋ช…ํ™•ํžˆ ์•Œ์•˜๋‹ค!

Operator๋Š” ๊ต‰์žฅํžˆ ๋‹ค์–‘ํ•˜๋‹ค. ์ด๊ฑธ ์ผ์ผํžˆ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ณต๋ถ€ํ•˜๊ธฐ ๋ณด๋‹ค๋Š”, ์™ ๋งŒํ•œ ๊ฑด ์ •์˜๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์ƒ๊ฐ๋‚  ๋•Œ๋งˆ๋‹ค ์ฐพ์•„์„œ ์ตํžˆ๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•์ผ ๋“ฏ ํ•˜๋‹ค.

Future & Publisher

Apple์€ Syncํ•˜๊ฒŒ ์‚ฌ์šฉํ–ˆ๋˜ Int์™€ Array๋ฅผ ๋†“๊ณ  Future์™€ Publisher๋ฅผ ๋น„๊ตํ–ˆ๋‹ค. ๋™๊ธฐ์ ์œผ๋กœ Int๊ฐ’์„ ์–ป๊ธฐ ์œ„ํ•ด์„œ๋Š” Int, ์—ฌ๋Ÿฌ๊ฐ’์„ ์›ํ–ˆ๋‹ค๋ฉด Array๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ, ๋น„๋™๊ธฐ์ ์œผ๋กœ. ์ฆ‰ ํ›„์— ๊ฐ’์ด ์™„๋ฃŒ๋œ ์‹œ๊ธฐ์— ๊ฐ’์„ ๋ฐ›๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋Š” ๋‹จ์ผ ๊ฐ’์˜ ๊ฒฝ์šฐ future, ์—ฌ๋Ÿฌ ๊ฐ’์˜ ๊ฒฝ์šฐ Publisher๋ฅผ ์‚ฌ์šฉํ•˜๋ผ ํ•œ๋‹ค.

Future์˜ ๊ฒฝ์šฐ, request ์‹œ์ ์— ํ•œ๋ฒˆ์˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์—, ๊ทธ๋ฆฌ๊ณ  Publsher์˜ ๊ฒฝ์šฐ N๋ฒˆ์˜ ๊ฐ’์„ ์‹œ๊ฐ„์— ๊ฑธ์ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ๋น„์œ ๋ฅผ ํ•œ ๊ฒƒ์ด ์•„๋‹Œ๊ฐ€ ํ•œ๋‹ค.

๋งˆ๋ฌด๋ฆฌ

  1. Subscriber๋Š” Publisher์—๊ฒŒ ๊ตฌ๋…ํ•˜๊ฒ ๋‹ค๊ณ  ์š”์ฒญํ•œ๋‹ค.
  2. Publisher๋Š” Subscription์„ ์ค€๋‹ค. (๊ตฌ๋…๊ถŒ!)
  3. ๊ตฌ๋…๊ถŒ์€ ๊ตฌ๋…์˜ Life cycle์„ ๊ด€๋ฆฌํ•œ๋‹ค. (์šฐ๋ฆฌ๊ฐ€ AnyCancellable๋กœ ๋ฐ›๋Š” ๋…€์„์ด ์ด๋…€์„. cancel()์ด๋ผ๋Š” ๋ฉ”์„œ๋“œ๋กœ ๊ตฌ๋… ์ทจ์†Œ ๊ฐ€๋Šฅ)

๊ฐ„๋‹จํ•˜๊ฒŒ Combine์ด ์–ด๋–ค ์›๋ฆฌ๋กœ ์ž‘๋™๋˜๋Š”์ง€์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜๋‹ค. Future, ์‹ค์ „ ์‚ฌ์šฉ๋ฒ• ๊ฐ™์ด ์“ธ ๊ธ€์ด ๋งŽ์€๋ฐ, ์ผ๋‹จ ์˜ค๋Š˜์€ ์—ฌ๊ธฐ์„œ ๋!

Reference