Combine: 개념과 Publisher 및 Subscriber 이해하기

저자: IT Sectr 게시일: 2026-03-17 읽는 시간: 8 분

Combine은 Apple이 iOS 13에서 도입한 반응형 프로그래밍 프레임워크입니다. Apple Documentation, 2025에 따르면, Combine은 비동기 이벤트 처리를 위한 통합 선언형 API를 제공합니다. Publisher는 데이터 소스를 정의하고, Subscriber는 이를 구독하여 데이터를 수신합니다.

핵심 요점

  • Combine — iOS 13부터 사용 가능한 Apple의 내장형 반응형 프로그래밍 프레임워크
  • Publisher — Failure 오류 가능성이 있는 Output 값의 소스를 정의하는 프로토콜
  • Subscriber — 수요 관리와 함께 Publisher로부터 값을 수신하는 프로토콜
  • Subject — 수동 이벤트 주입이 가능한 Publisher (PassthroughSubject, CurrentValueSubject)
  • Operators — 스트림 변환 및 결합을 위한 map, filter, combineLatest, flatMap

Combine이란?

Combine은 시간에 따라 비동기 이벤트를 처리하기 위한 선언형 Swift 프레임워크입니다. 콜백 기반 접근 방식과 달리, Combine은 연산자 체인을 통해 데이터 처리 파이프라인을 설명할 수 있습니다. 이 프레임워크는 Foundation(URLSession, Timer, NotificationCenter) 및 SwiftUI(ObservableObject, @Published)와 통합됩니다.

Publisher 프로토콜

Publisher 프로토콜을 구현하는 모든 타입은 Output(값 타입)과 Failure(오류 타입)를 정의해야 합니다. Publisher는 구독될 때까지 활성화되지 않습니다 — subscribe가 호출된 후에만 이벤트를 방출하기 시작합니다. 이는 반응형 스트림의 특징인 콜드 의미론으로, 효율성을 향상시킵니다.

Subscriber 프로토콜

Subscriber는 세 가지 메서드를 통해 이벤트를 수신합니다: receive(subscription:) — 수요 관리와 함께 구독 확인; receive(_:) — 새 값 수신; receive(completion:) — 성공 또는 오류로 스트림 완료. Demand는 Subscriber가 수락할 준비가 된 값의 수를 지정합니다 — 이것이 백프레셔 메커니즘입니다.

내장 Publisher

Apple은 다양한 내장 Publisher를 제공합니다: Just는 단일 값을 방출하고 완료됩니다. Future는 클로저를 통한 비동기 결과, Deferred는 구독 시점까지 Publisher 생성을 지연합니다. URLSession.dataTaskPublisher는 네트워크 요청을 반응형 스트림으로 변환합니다. Timer.publish는 주기적인 타이머를 만듭니다. NotificationCenter.default.publisher는 알림을 Publisher로 변환합니다. 이러한 통합을 통해 수동 브리징 없이 이벤트를 구독할 수 있습니다.

Combine의 Publisher와 Subscriber

Combine에서 Publisher와 Subscriber는 Subscription 프로토콜을 통해 연결됩니다. Subscription은 데이터 흐름을 제어하는 연결 객체입니다. 개발자는 자체 Publisher 타입을 만들 필요가 없습니다 — Apple이 내장 타입을 제공합니다: Just, Future, Deferred, Fail, Empty, 그리고 URLSession과 Timer용 Publisher.

Combine의 Subject

Subject는 수동으로 값을 보낼 수 있는 Publisher입니다. PassthroughSubject는 상태를 저장하지 않습니다: 구독자는 구독 이후에 발생한 이벤트만 수신합니다. CurrentValueSubject는 RxSwift의 BehaviorRelay와 유사하게 현재 값을 저장하고 새 구독자에게 즉시 전달합니다.

백프레셔

Combine의 백프레셔 메커니즘은 데이터 전송 속도를 조절합니다. Subscriber는 subscription.request(.unlimited) 또는 subscription.request(.max(N))를 통해 Publisher에게 처리할 준비가 된 값의 수를 알립니다. 이는 생산자와 소비자의 속도가 불균형할 때 버퍼 오버플로를 방지합니다.

Combine 연산자

Combine에는 100개 이상의 연산자가 있으며, 범주로 나뉩니다: 변환(map, tryMap, flatMap), 필터링(filter, compactMap, removeDuplicates), 결합(combineLatest, merge, zip), 시간 관리(debounce, throttle, delay), 오류 처리(catch, replaceError, retry).

combineLatest 연산자는 두 Publisher의 최신 값을 결합합니다 — 둘 중 하나가 새 값을 방출할 때마다 두 업데이트된 값으로 클로저가 호출됩니다. 이 연산자는 여러 입력 필드의 상태를 동시에 추적해야 하는 양식 유효성 검사에 필수적입니다.

debounce 연산자는 새 이벤트 없이 지정된 간격이 경과할 때까지 값 게시를 지연합니다. 이는 검색 필드에 매우 중요합니다: 사용자가 300–500ms 동안 입력을 중단한 후에만 서버 요청이 전송되어 부하가 5–10배 감소합니다.

오류 처리

Combine은 연산자 수준에서 여러 오류 처리 전략을 제공합니다. catch는 Failure를 가로채고 Publisher를 폴백으로 대체합니다 — 스트림은 충돌 없이 계속됩니다. replaceError(with:)는 오류 대신 기본값을 제공합니다. retry(_:)는 실패 시 지정된 횟수만큼 구독을 재시도합니다. 중요한 점은 Publisher 시그니처의 Never 오류 타입이 스트림이 절대 오류로 종료되지 않음을 보장합니다 — SwiftUI에서 Failure 처리 없이 Publisher를 사용할 수 있습니다.

Combine 코드 예제

첫 번째 예제는 Just의 기본 사용법을 보여줍니다 — 단일 값을 방출하고 완료되는 Publisher입니다:

swift
let publisher = Just("Hello Combine")

publisher
    .sink(receiveCompletion: { completion in
        print("완료: \(completion)")
    }, receiveValue: { value in
        print("수신: \(value)")
    })

두 번째 예제는 메인 스레드에서 결과 처리를 포함한 네트워크 요청을 위해 URLSession과 Combine 통합을 보여줍니다:

swift
let url = URL(string: "https://api.example.com")!

URLSession.shared
    .dataTaskPublisher(for: url)
    .map { $0.data }
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("오류: \(error)")
        }
    }, receiveValue: { data in
        print("수신한 데이터: \(data.count)")
    })

세 번째 예제는 상태 관리를 위한 CurrentValueSubject 사용을 보여줍니다:

swift
let counter = CurrentValueSubject<Int, Never>(0)

counter
    .sink { value in
        print("카운터: \(value)")
    }

counter.send(1) // 출력: 카운터: 1
counter.send(2) // 출력: 카운터: 2
print(counter.value) // 출력: 2

Combine vs RxSwift

Combine과 RxSwift는 동일한 문제 — 반응형 프로그래밍 — 를 해결하지만 다른 접근 방식을 사용합니다. Combine은 시스템에 내장된 Apple 네이티브 프레임워크입니다: 종속성 설치 불필요, SwiftUI 및 Foundation과 통합됨. RxSwift는 iOS 9+를 지원하는 타사 라이브러리로 더 넓은 연산자 세트를 제공합니다.

네이티브 C 구현 덕분에 일반적인 시나리오에서 Combine의 성능이 더 높습니다. RxSwift는 더 많은 디버깅 도구(RxSwift.Resources.total, Debug)를 제공하지만, 객체 지향 아키텍처는 대규모 데이터 스트림에서 효율성이 떨어질 수 있습니다. 둘 사이의 선택은 기능과 통합 간의 균형입니다.

중요한 차이점은 백프레셔 모델입니다. Combine에서는 Subscriber가 Subscription을 통해 수요를 관리하여 스트림 속도를 명시적으로 제어합니다. RxSwift에서는 백프레셔가 기본적으로 제어되지 않습니다 — Observable이 모든 값을 방출하고 Subscriber가 도착하는 대로 처리합니다. 대규모 데이터 스트림의 경우 Combine이 더 예측 가능하고 메모리 안전합니다.

Combine은 언어 수준에서 SwiftUI와 깊이 통합되어 있습니다. @Published는 Publisher를 생성하고, ObservableObject는 objectWillChange Publisher를 제공합니다. SwiftUI View는 자동으로 이러한 Publisher를 구독하고 변경 시 다시 그립니다. UIKit용으로 CombineCocoa와 같은 타사 래퍼가 있지만, Combine은 원래 SwiftUI용으로 설계되었으므로 UIKit에서의 통합에는 추가 코드가 필요합니다. 반면 RxSwift는 UIKit을 위한 풍부한 생태계를 가지고 있습니다: RxCocoa는 모든 UI 요소에 대한 반응형 래퍼를 제공합니다.

Combine은 NSFetchedResultsController Publisher를 통해 Core Data와의 통합을 지원합니다. Publisher는 Core Data 스택 변경(레코드 추가, 삭제, 업데이트) 시마다 새 데이터를 방출합니다. 이를 통해 수동으로 reloadData를 호출하지 않고도 반응형 목록을 구축할 수 있습니다. Combine을 UIApplication.willResignActiveNotification용 NotificationCenter Publisher와 결합하여 개발자는 델리게이트와 콜백 없이 전체 애플리케이션 수명 주기에 대한 반응형 계층을 만듭니다. ObservableObject 클래스의 경우 @Published 속성이 변경되면 SwiftUI가 자동으로 View를 업데이트합니다 — Combine은 수동 코드 한 줄 없이 전체 체인을 처리합니다. Combine 코드의 단위 테스트는 ImmediateScheduler를 통한 Scheduler 격리와 함께 테스트 케이스에서 XCTestExpectation 및 Publisher.sink를 통해 수행됩니다.

UIKit 프로젝트에서 Combine으로 작업할 때 개발자는 종종 sink를 통해 구독하고 AnyCancellable을 Set에 저장합니다. 일반적인 패턴: ViewController가 뷰를 만들고, cancellables를 저장하고, ViewModel의 Publisher를 구독합니다. deinit 시 모든 구독은 Cancellables 컬렉션을 통해 자동으로 취소됩니다. Combine에서 UIControl을 올바르게 처리하려면 publisher(for:) 확장을 통해 UIControl.Event Publisher를 사용할 수 있습니다.

Combine 디버깅은 print() 또는 handleEvents() 연산자를 통해 수행됩니다. print(String)은 모든 이벤트를 기록합니다: receive subscription, request demand, receive value, receive completion. handleEvents는 더 세밀한 제어를 제공합니다: Publisher 수명 주기의 각 단계에 대한 클로저를 지정할 수 있습니다. 테스트를 위해 CombineExpectations 라이브러리가 있습니다 — 가상 시간과 수요 제어를 사용하여 Publisher가 테스트에서 예상 값을 방출하는지 확인할 수 있습니다.

Combine 실제 활용

Combine은 SwiftUI에서 양식 관리에 적극적으로 사용됩니다: 각 @Published 속성은 Publisher를 생성하고, SwiftUI는 View.body를 통해 이를 구독합니다. combineLatest를 통해 여러 Publisher를 결합하면 양식 유효성 검사가 선언형이 됩니다: 각 Publisher는 하나의 필드를 추적하고, combineLatest가 모든 값을 수집하며, map이 유효성을 검사하고 Submit 버튼 상태를 반환합니다. Publishers.Merge는 동일한 타입의 여러 Publisher를 하나로 결합합니다 — 여러 컨트롤의 이벤트를 단일 스트림으로 수집하는 데 유용합니다.

UIKit과 Combine 통합을 위해 확장을 통해 UIControl Publisher를 사용하세요: button.publisher(for: .touchUpInside)는 탭 시 이벤트를 방출하는 Publisher를 반환합니다. CombineCocoa는 모든 UIControl 이벤트, UITextView.text, UIScrollView.contentOffset에 대한 Publisher를 제공하는 커뮤니티 라이브러리입니다. 구독 취소 시 cancellables 저장소는 deinit에서 지워져 어떤 구독도 소유자보다 오래 지속되지 않음을 보장합니다. 여러 상태를 가진 UIControl의 경우 Publishers.MergeMany를 사용하여 Publisher를 배열로 결합하세요.

자주 묻는 질문

Combine은 어떤 iOS 버전부터 사용할 수 있나요?

Combine은 iOS 13, macOS 10.15, tvOS 13, watchOS 6부터 사용할 수 있습니다. 이는 이전 버전 지원이 필요한 프로젝트에서 사용이 제한됩니다. iOS 12 이하에서는 RxSwift 또는 타사 라이브러리가 사용됩니다.

PassthroughSubject와 CurrentValueSubject의 차이점은?

PassthroughSubject는 상태를 저장하지 않으며 새 구독자에게 마지막 값을 재전송하지 않습니다 — 미래 이벤트만 수신합니다. CurrentValueSubject는 현재 값을 저장하고 새 구독자에게 즉시 전달합니다. CurrentValueSubject.value를 사용하면 현재 값을 동기적으로 읽고 쓸 수 있습니다.

Combine은 SwiftUI와 어떻게 통합되나요?

Combine은 SwiftUI의 핵심입니다: @Published가 Publisher를 생성하고, ObservableObject는 objectWillChange Publisher를 사용합니다. SwiftUI View는 Combine 파이프라인을 통해 @ObservedObject와 @StateObject를 자동으로 구독하고, 속성이 변경되면 View를 다시 그립니다.

Combine에서 백프레셔란 무엇인가요?

백프레셔는 데이터 전송 속도를 제어하는 메커니즘입니다. Subscriber는 Subscription에게 처리할 준비가 된 값의 수(수요)를 알립니다. Publisher는 요청된 것보다 더 많이 방출할 수 없습니다. 네트워크 소켓의 대규모 데이터 스트림 작업 시 중요합니다.

Combine에서 오류를 어떻게 처리하나요?

catch 연산자는 오류를 가로채고 Publisher를 폴백으로 대체합니다. replaceError는 오류를 기본값으로 대체합니다. retry는 오류 시 지정된 횟수만큼 구독을 재시도합니다. 모든 연산자는 Failure 시 충돌을 방지하기 위해 subscribe 전에 배치해야 합니다.

요약

  • Combine — iOS 13부터 반응형 프로그래밍을 위한 Apple 내장 프레임워크
  • Publisher — Output과 Failure가 있는 프로토콜, 구독 후 작업 시작
  • Subscriber — 수요 관리 지원과 함께 receive를 통해 값 수신
  • Subject (PassthroughSubject, CurrentValueSubject) — 수동 이벤트 주입이 가능한 Publisher
  • 연산자 map, filter, combineLatest, debounce, catch — 파이프라인의 기초
  • 백프레셔는 수요를 통해 Subscriber 버퍼 오버플로를 방지
  • iOS 13+ 대상 신규 프로젝트는 Combine 선택, 이전 버전 지원은 RxSwift

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기