데이터를 생성하는 Publisher
오늘은 Combine의 세 가지 핵심 요소 중 첫 번째인 Publisher에 대해 이야기해보려고 합니다.
Combine은 시간에 따라 변하는 값을 다루기 위한 Apple의 선언적 프레임워크로, 다양한 비동기 작업을 더 쉽고 일관된 방식으로 처리할 수 있게 해줍니다. 이 중에서도 Publisher는 데이터를 만들어내는 출발점이기 때문에, Combine을 이해하는 데 매우 중요한 개념입니다.
Apple 공식 문서에서는 Publisher를 다음과 같이 설명합니다:
Declares that a type can transmit a sequence of values over time.
(시간에 따라 변할 수 있는 값들을 외부에 전달할 수 있는 타입임을 선언한다)
즉, Publisher는 시간의 흐름에 따라 발생하는 값들을 외부로 전달하는 역할을 합니다.
이 설명만 봐도 대략 어떤 역할을 하는지 감이 오시죠?
이제 Publisher가 실제로 어떤 방식으로 동작하는지, 그리고 어떻게 활용할 수 있는지 차근차근 살펴보겠습니다.
Publisher의 기본 정의
Combine에서 Publisher는 데이터를 만들어내는 출발점입니다. 말 그대로 데이터의 공급자 역할을 하죠.
Publisher는 값을 하나 이상 emit(방출) 하고, 그 후 정상적으로 완료하거나, 에러를 통해 종료될 수 있습니다.
Apple 공식 문서에 따르면 Publisher는 아래처럼 정의됩니다:
protocol Publisher {
associatedtype Output
associatedtype Failure: Error
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input
}
즉, 어떤 타입의 값을 방출할 건지(Output), 어떤 에러 타입을 방출할 수 있는지(Failure)를 명확하게 정의해야 합니다.
또한, Subscriber가 필요로 하는 만큼의 데이터를 제공할 수 있도록 양방향 커뮤니케이션도 지원하죠.
Publisher는 두 가지 방식으로 동작합니다
- 내부에서 값을 직접 정의하고 방출하는 Publisher
- 외부에서 값을 주입받아 방출하는 Publisher
1. 내부에서 값을 정의하고 방출하는 방식
이 방식은 Publisher 자신이 값을 정하고, 언제 방출할지 스스로 결정합니다.
외부에서는 값을 주입하거나 변경할 수 없습니다. 대표적으로 아래 두 가지가 있습니다.
🔹 Just — 단일 값 방출 후 종료
import Combine
let publisher = Just("Hello")
publisher
.sink { value in
print("받은 값: \(value)")
}
// 출력: 받은 값: Hello
Just는 단 하나의 값을 방출하고 즉시 완료됩니다. 테스트나 초기 값 전달에 유용합니다.
🔹 Future — 비동기 작업을 Combine으로 래핑
Future는 함수에 주로 사용이 됩니다.
Combine 이전에는 이렇게 썼다면:
func loadData(completion: @escaping (String) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
completion("불러온 데이터")
}
}
이제는 Future를 활용해 Combine과 연결할 수 있습니다:
import Combine
func loadDataWithFuture() -> Future<String, Never> {
return Future { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
promise(.success("불러온 데이터"))
}
}
}
이렇게 하면 이후 .sink, .map, .flatMap 등으로 자유롭게 조합하여 사용할 수 있어요.
2. 외부에서 값을 주입하는 방식 — Subject
Subject는 Combine에서 조금 특별한 존재입니다.
Publisher이면서도 외부에서 값을 직접 주입할 수 있는 구조입니다.
📌 즉, Subject = Publisher + 외부 값 주입 가능
대표적인 두 가지 Subject가 있습니다:
🔹 CurrentValueSubject — 최신 값을 저장하고, 구독 즉시 전달
import Combine
let current = CurrentValueSubject<String, Never>("초기값")
let subscriber1 = current.sink {
print("subscriber1 받은 값: \($0)")
}
current.send("new 값")
let subscriber2 = current.sink {
print("subscriber2 받은 값: \($0)")
}
// 출력:
// subscriber1 받은 값: 초기값
// subscriber1 받은 값: new 값
// subscriber2 받은 값: new 값
항상 초기값이 존재하고 구독자가 생기면 즉시 현재 값을 전달합니다.
🔹 PassthroughSubject — 외부에서 실시간으로 값을 보낼 때
import Combine
let passthrough = PassthroughSubject<String, Never>()
let subscriber1 = passthrough.sink {
print("subscriber1 받은 값: \($0)")
}
passthrough.send("첫번째 값")
passthrough.send("두번째 값")
// 출력:
// subscriber1 받은 값: 첫번째 값
// subscriber1 받은 값: 두번째 값
초기값은 없고 구독자가 생긴 후부터 .send()를 통해 실시간으로 값을 전달합니다.
마무리
이처럼 Combine의 Publisher는 다양한 방식으로 값을 생성하고, 전달할 수 있습니다.
특히 내부에서 값을 방출하는 방식과 외부에서 주입받는 방식은 Combine을 사용할 때 큰 차이를 만들어내므로, 각각의 특성을 잘 이해해두는 것이 중요합니다.
다음 글에서는 Subscriber나 Operator에 대해 좀 더 깊이 다뤄보겠습니다 :)
궁금한 점이나 피드백은 언제든 댓글로 남겨주세요! 부족한 내용이지만 끝까지 읽어봐주셔서 감사합니다.
오늘도 개발자를 위한 명언으로 마무리하도록 하겠습니다! 파이팅입니다~
Write code that is easy for machines to read, and for humans to modify.
(코드를 작성할 때는 기계가 읽기 편하게, 사람이 수정하기 편하게 작성하라. (앤디 헌)
'iOS 개발 > Combine' 카테고리의 다른 글
[Combine] Operator Guide (1) | 2025.05.02 |
---|---|
[Combine] Operator, Scheduler (2) | 2025.05.02 |
[Combine] 데이터를 처리하는 Subscriber (0) | 2025.04.28 |
[Combine] 왜 Combine을 알아야 할까? (0) | 2025.04.23 |