[Combine] 데이터를 처리하는 Subscriber

2025. 4. 28. 10:00·iOS 개발/Combine

데이터를 처리하는 Subscriber

이전 글에서는 Combine의 세 가지 핵심 요소 중 첫 번째, Publisher에 대해 다뤘습니다.

오늘은 두 번째 요소인 Subscriber에 대해 이야기해보려고 합니다.

 

Apple 공식 문서에서는 Subcriber를 다음과 같이 설명합니다:

A protocol that declares a type that can receive input from a publisher.
(Publisher로부터 입력을 받을 수 있는 타입을 선언하는 프로토콜)

 

Publisher가 데이터를 생성하고 방출하는 주체라면,

Subscriber는 Publisher가 내보낸 데이터를 받아서 소비하고 처리하는 주체입니다.

 

이번 글에서는 Subscriber를 중심으로,

Subscription의 개념과 Publisher와 Subscriber 간의 데이터 흐름(Pattern) 까지
차근차근 함께 살펴보겠습니다.


Subscriber의 기본 정의

Subscriber는 다음과 같은 프로토콜을 따릅니다.

protocol Subscriber {
    associatedtype Input
    associatedtype Failure: Error

    func receive(subscription: Subscription)
    func receive(_ input: Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Failure>)
}

프로토콜 정의를 살펴보면, Subscriber는 Publisher에게 데이터를 요청하고, 어떤 타입의 값을 받을 것인지(Input) 와 어떤 타입의 에러를 처리할 것인지(Failure) 를 명확히 정의해야 합니다.

 

추가로, 구독한 이후에는 필요한 데이터의 수를 요청할 수 있으며, 더 이상 데이터를 받고 싶지 않다면 구독을 중단(cancel) 하여Publisher에게 값을 더 이상 보내지 말라는 의사를 전달할 수도 있습니다.

 


Subscriber의 기본 제공 타입

Combine에서는 직접 Subscriber를 구현하지 않고도 사용할 수 있도록 assign과 sink 같은

기본 Subscriber를 제공합니다.

또한, 커스텀 Subscriber를 만들 때는 demand를 통해 수신할 데이터의 양을 제어하고, completion을 통해

데이터 스트림의 종료를 처리할 수 있습니다.

 

이번에는 각각을 하나씩 살펴보겠습니다.

1️⃣ assign(to:on:) — 객체 속성에 자동으로 값 할당

assign은 Publisher가 방출한 데이터를 특정 객체의 속성(KeyPath) 에 자동으로 할당해주는 기능입니다.

주로 UI 업데이트 (예: label.text, viewModel.value) 처럼 객체의 속성을 실시간으로 반영할 때 많이 사용합니다.

import Combine

let arrayPublisher = [1, 3, 5, 7, 9].publisher

class MyClass {
    var property: Int = 0{
        didSet{
            print("Update Property: \(property)")
        }
    }
}

let object = MyClass()
let subscription1 = arrayPublisher.assign(to: \.property, on: object)
//출력:
//Update Property: 1
//Update Property: 3
//Update Property: 5
//Update Property: 7
//Update Property: 9

✔️ assign(to:on:)을 통해 arrayPublisher가 방출하는 값이 MyClass 객체의 property에 자동으로 할당됩니다.

 

2️⃣ sink(receiveValue:) — 직접 클로저로 처리하기

sink는 Publisher가 방출하는 값을 내가 직접 정의한 클로저로 받아 처리할 수 있게 해줍니다.

UI 업데이트, 디버깅, 특정 로직 실행 등 다양한 상황에 매우 유연하게 활용됩니다.

import Combine

let publisher = Just("방출된 값")

let subscription2 = publisher
    .sink { value in
        print("받은 값: \(value)")
    }

// 출력: 받은 값: 방출된 값

✔️ 받은 값을 자유롭게 가공하거나, 필요한 작업을 바로 처리할 수 있습니다.

 

3️⃣ demand — 받을 데이터 수 제어하기

demand는 커스텀 Subscriber를 만들 때, 얼마나 많은 값을 받을지 제어하는 장치입니다.

  • 첫 번째 receive(subscription:)에서 request를 통해 요청
  • 이후 receive(_:)에서 추가로 받을지 결정
import Combine

// 1️⃣ 커스텀 Subscriber 정의
final class MyIntSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never

    func receive(subscription: Subscription) {
        print("🔗 구독 시작")
        subscription.request(.max(2)) // 최대 2개만 받을게요
    }

    func receive(_ input: Int) -> Subscribers.Demand {
        print("📦 값 받음: \(input)")
        return .none // 더 이상 받지 않음 (추가 요청 안 함)
    }

    func receive(completion: Subscribers.Completion<Never>) {
        print("✅ 완료: \(completion)")
    }
}

// 2️⃣ Publisher 생성
let publisher = [10, 20, 30, 40, 50].publisher

// 3️⃣ 구독
let subscriber = MyIntSubscriber()
publisher.subscribe(subscriber)

//출력:
//🔗 구독 시작
//📦 값 받음: 10
//📦 값 받음: 20
//
//✅ 완료: finished -> publisher의 값을 다 받은 경우에 출력이됨 즉 request값을 .max(5)로 하거나 .unlimited하면 완료가 뜰것이다.  )

✔️ .max(2)로 요청했기 때문에, 2개만 받고 완료됩니다. return .none → 여기서 추가 요청은

안한다는 의미이며 .max(1) 또는 .unlimited로 반환하면 더 받을 수도 있습니다. 

 

4️⃣ completion — 데이터 전송 완료 알림

Publisher는 모든 데이터를 방출한 후, 반드시 완료 신호(Completion) 를 Subscriber에게 보냅니다.

  • .finished: 정상 완료
  • .failure(Error): 오류 발생

✔️ Completion을 통해 Subscriber는 스트림이 끝났다는 걸 알 수 있습니다.

 

추가 개념: AnySubscriber — 타입 지우기용 Subscriber

AnySubscriber는 다양한 타입의 Subscriber를 하나의 일반적인 형태로 감싸주는 타입입니다.

제네릭 Subscriber를 추상화하거나 클로저로 간편하게 Subscriber를 만들고 싶을 때 유용합니다.

import Combine

let subscriber = AnySubscriber<Int, Never>(
    receiveSubscription: { subscription in
        subscription.request(.unlimited)
    },
    receiveValue: { value in
        print("받은 값: \(value)")
        return .unlimited
    },
    receiveCompletion: { completion in
        print("완료: \(completion)")
    }
)

let pub = [1, 2, 3].publisher
pub.subscribe(subscriber)

✔️ 별도의 클래스를 만들지 않고, 클로저로 간단히 Subscriber를 구성할 수 있습니다.


Subscription이란?

Subscription은 Subscriber와 Publisher가 연결되었음을 나타내는 객체입니다.

일종의 구독권(티켓) 같은 존재이며 Subscription이 살아있는 동안만 데이터가 오갑니다. 

필요 시 cancel() 메서드로 구독을 중단할 수 있습니다.

import Combine

let publisher = Just("데이터")
let subscription = publisher
    .sink { print("받은 값: \($0)") }

subscription.cancel() // 구독 중단!

 

구독(Subscribe) vs 구독권(Subscription)의 관계

앞서 살펴본 Subscription 개념을 이해하기 위해서는, Subscribe와 Subscription의 차이를

확실히 구분하는 것이 중요합니다.

 

Subscribe는(즉, Publisher와 연결을 시작하는 ‘행동’입니다.)

👉 Publisher를 sink하거나 assign하는 구독하는 행위를 의미합니다.

 

Subscription은(Publisher ↔ Subscriber 간의 연결을 유지하는 티켓 역할을 합니다.)

👉 구독 행위의 결과로 생성되는 객체입니다.

 

✔️ 쉽게 정리하면, Subscribe는 행동, Subscription은 그 행동을 통해 만들어진 연결 객체라고 이해할 수 있습니다.


Publisher & Subscriber 패턴

Combine에서는 Publisher와 Subscriber가 아래와 같은 정형화된 흐름(Pattern) 을 따릅니다.

 

👉 전체 흐름 요약

  1. Subscriber가 Publisher에 attach (구독 시작)
  2. Publisher가 Subscription 객체를 전달
  3. Subscriber가 받고 싶은 값의 수(N) 를 요청
  4. Publisher가 N개의 값 또는 그 이하를 전송
  5. Publisher가 완료(completion) 를 전송하며 종료

마무리

이번 글에서는 Combine의 두 번째 핵심 요소인 Subscriber에 대해 살펴보았습니다.

 

정리하면,

  • Publisher는 데이터를 생성하고 방출하는 주체
  • Subscriber는 방출된 데이터를 받아 소비하고 처리하는 주체
  • assign과 sink를 통해 Subscriber를 쉽게 사용할 수 있다
  • demand와 completion으로 데이터 수신량과 완료를 제어할 수 있다
  • Subscription은 Publisher와 Subscriber 간 연결을 관리한다
  • Publisher & Subscriber는 정형화된 패턴을 통해 데이터를 주고받는다

 

Combine을 제대로 활용하기 위해서는

Publisher → Subscription → Subscriber 흐름을 정확히 이해하는 것이 매우 중요합니다

 

궁금한 점이나 피드백은 언제든 댓글로 남겨주세요! 부족한 내용이지만 끝까지 읽어봐주셔서 감사합니다. 
오늘도 개발자를 위한 명언으로 마무리하도록 하겠습니다! 파이팅입니다~

Code is a work of art, and above all, it should be readable.
코드는 작품이다. 무엇보다도 그 작품이 가독성이 좋아야 한다.(Bjarne Stroustrup)

'iOS 개발 > Combine' 카테고리의 다른 글

[Combine] Operator Guide  (1) 2025.05.02
[Combine] Operator, Scheduler  (2) 2025.05.02
[Combine] 데이터를 생산하는 Publisher  (0) 2025.04.25
[Combine] 왜 Combine을 알아야 할까?  (0) 2025.04.23
'iOS 개발/Combine' 카테고리의 다른 글
  • [Combine] Operator Guide
  • [Combine] Operator, Scheduler
  • [Combine] 데이터를 생산하는 Publisher
  • [Combine] 왜 Combine을 알아야 할까?
Riu
Riu
안녕하세요 iOS 개발자를 꿈꾸는 Riu입니다. Github: woolnd
  • Riu
    Riu 개발노트
    Riu
  • 전체
    오늘
    어제
    • 분류 전체보기 (27) N
      • 티스토리 (2)
      • iOS 개발 (21) N
        • SwiftUI (9)
        • UIKit (6)
        • Combine (5)
        • Architecture (1) N
      • 알고리즘 (1)
      • 회고록 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    SWIFT
    회고록
    시작
    Combine
    UIKit
    막자알림서비스
    cleanArchitecture
    나만의todo
    SWIF
    ios
    앗차!
    SwiftUI
    figma
    Architecture
    안드로이드
    티스토리
    ios개발
    알고리즘
    구름톤유니브
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
Riu
[Combine] 데이터를 처리하는 Subscriber
상단으로

티스토리툴바