Combine Swift에서 connect()와 autoconnect()의 숨겨진 힘
Source: Dev.to
번역할 텍스트를 제공해 주시면, 원본 서식과 마크다운을 그대로 유지하면서 한국어로 번역해 드리겠습니다.
Introduction
Combine 퍼블리셔를 구독하면 값이 즉시 흐르기 시작할 것으로 기대할 수 있습니다. 그러나 일부 퍼블리셔는 작업을 시작하기 전에 명시적인 신호를 기다립니다. Connectable 퍼블리셔는 이러한 제어를 제공하며, connect()와 autoconnect()가 이를 시작하는 두 가지 방법입니다.
connect() – Manual Control
connectable publisher는 connect() 메서드를 호출하기 전까지는 아무 일도 하지 않습니다. 이를 통해 다음을 할 수 있습니다:
- 값이 방출되기 전에 여러 구독자를 설정합니다.
- 다른 앱 이벤트와 데이터 흐름 시작을 조정합니다.
- 준비가 될 때까지 비용이 많이 드는 작업을 미룹니다.
Example
import Combine
let subject = PassthroughSubject<Int, Never>()
// Create a connectable publisher
let connectable = subject
.print("Debug")
.makeConnectable()
// Subscribe first
let subscription1 = connectable
.sink { value in
print("Subscriber 1 received: \(value)")
}
let subscription2 = connectable
.sink { value in
print("Subscriber 2 received: \(value)")
}
// Nothing happens yet, even though we have subscribers
// Manually start the publisher
let connection = connectable.connect()
// Now values flow to all subscribers
subject.send(1)
subject.send(2)
Key points
- Manual start – 퍼블리셔가 언제 시작될지 정확히 결정할 수 있습니다.
- Multiple subscribers – 연결되면 모든 구독자가 값을 받습니다.
- Cancellable –
connection을 취소하여 흐름을 중단할 수 있습니다.
When to use connect()
- 여러 구독자가 준비될 때까지 기다려야 할 때.
- 시작 시점을 다른 이벤트와 동기화하고 싶을 때.
- 업스트림 작업이 비용이 많이 들고 한 번만 실행되어야 할 때.
Real‑world scenario: shared network request
let url = URL(string: "https://example.com/user")!
let publisher = URLSession.shared
.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: User.self, decoder: JSONDecoder())
.share()
.makeConnectable()
// Set up multiple subscribers
let sub1 = publisher.sink(
receiveCompletion: { _ in },
receiveValue: { user in updateUI(user) }
)
let sub2 = publisher.sink(
receiveCompletion: { _ in },
receiveValue: { user in saveToCache(user) }
)
// Only one network request is performed
let connection = publisher.connect()
autoconnect() – 자동 시작
autoconnect()는 수동 connect() 호출이 필요 없게 합니다. 퍼블리셔는 첫 번째 구독자가 나타나는 즉시 자동으로 연결됩니다.
예시
let timer = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
let subscription = timer.sink { date in
print("Timer fired at: \(date)")
}
핵심 포인트
- 즉시 시작 – 퍼블리셔는 첫 구독자를 받는 즉시 시작합니다.
- 편리함 – 별도의 연결을 저장할 필요가 없습니다.
- 타이머와 일반적으로 사용 – 주기적인 이벤트에 이상적입니다.
autoconnect()를 사용할 때
- 구독 시 바로 퍼블리셔가 시작되길 원할 때.
- 타이머나 다른 주기적 소스를 다룰 때.
- 여러 구독자 간의 정밀한 조정이 필요하지 않을 때.
ViewModel 예시
class ViewModel: ObservableObject {
@Published var seconds = 0
private var cancellables = Set<AnyCancellable>()
func startTimer() {
Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.seconds += 1
}
.store(in: &cancellables)
}
}
connect()와 autoconnect() 중 선택하기
| 요구 사항 | connect() 사용 | autoconnect() 사용 |
|---|---|---|
| 여러 구독자를 기다려야 함 | ✅ | ❌ |
| 구독 시 즉시 시작 | ❌ | ✅ |
| 타이머 또는 주기적 이벤트 | ❌ (가능) | ✅ |
| 비용이 많이 드는 공유 작업 | ✅ | ✅ (즉시 시작이 괜찮다면) |
| 간단한 사용 사례 | ❌ | ✅ |
| 다른 이벤트와의 조정 | ✅ | ❌ |
단일 구독 공유
종종 share()(연결 가능한 퍼블리셔를 생성함)와 connect() 또는 autoconnect()를 결합하여 여러 하위 구독자가 동일한 업스트림 작업을 공유하도록 합니다.
let shared = expensivePublisher
.share() // Makes the publisher connectable
.autoconnect() // Starts automatically (or use .connect() manually)
let sub1 = shared.sink { print("A: \($0)") }
let sub2 = shared.sink { print("B: \($0)") }
연결 및 구독 저장
연결과 cancellable 객체에 대한 참조를 유지해야 합니다. 그렇지 않으면 객체가 해제되고 퍼블리셔가 중지됩니다.
class DataManager {
private var connection: Cancellable?
private var subscriptions = Set<AnyCancellable>()
func setupConnectable() {
let connectable = publisher.makeConnectable()
connectable
.sink { value in print(value) }
.store(in: &subscriptions)
// Store the connection so it can be cancelled later
connection = connectable.connect()
}
deinit {
connection?.cancel()
}
}
결론
connect()와 autoconnect()는 Combine 파이프라인이 값을 방출하기 시작하는 시점을 정확하게 제어할 수 있게 합니다. 명시적인 타이밍과 조정이 필요할 때는 connect()를 사용하고, 단순함과 즉시 시작을 선호할 때—특히 타이머나 기타 주기적인 퍼블리셔와 함께—autoconnect()를 사용하세요. 이러한 도구들을 이해하면 Swift 애플리케이션에서 리소스 사용과 데이터 흐름을 보다 효율적으로 관리할 수 있습니다.