The Hidden Power of connect() and autoconnect() in Combine Swift

Published: (December 27, 2025 at 01:37 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Introduction

When you subscribe to a Combine publisher, you might expect values to start flowing immediately. However, some publishers wait for an explicit signal before they begin work. Connectable publishers give you that control, and connect() and autoconnect() are the two ways to start them.

connect() – Manual Control

A connectable publisher does nothing until you call its connect() method. This lets you:

  • Set up multiple subscribers before any values are emitted.
  • Coordinate the start of data flow with other app events.
  • Avoid expensive work until you’re ready.

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 – you decide exactly when the publisher begins.
  • Multiple subscribers – all receive values once connected.
  • Cancellable – the connection can be cancelled to stop the flow.

When to use connect()

  • You need to wait until several subscribers are ready.
  • You want to synchronize the start with other events.
  • The upstream work is expensive and should run only once.

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() – Automatic Start

autoconnect() removes the need for a manual connect() call. The publisher automatically connects as soon as the first subscriber appears.

Example

let timer = Timer.publish(every: 1.0, on: .main, in: .common)
    .autoconnect()

let subscription = timer.sink { date in
    print("Timer fired at: \(date)")
}

Key points

  • Immediate start – the publisher begins as soon as it gets its first subscriber.
  • Convenient – no need to store a separate connection.
  • Common with timers – ideal for periodic events.

When to use autoconnect()

  • You want the publisher to start right away upon subscription.
  • You’re dealing with timers or other periodic sources.
  • Precise coordination of multiple subscribers isn’t required.

ViewModel example

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)
    }
}

Choosing Between connect() and autoconnect()

RequirementUse connect()Use autoconnect()
Need to wait for multiple subscribers
Immediate start on subscription
Timer or periodic events❌ (possible)
Expensive shared operation✅ (if immediate start is fine)
Simple use case
Coordination with other events

Sharing a Single Subscription

Often you’ll combine share() (which creates a connectable publisher) with either connect() or autoconnect() to ensure multiple downstream subscribers share the same upstream work.

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)") }

Storing Connections and Subscriptions

Remember to keep references to your connections and cancellables, otherwise they’ll be deallocated and the publisher will stop.

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()
    }
}

Conclusion

connect() and autoconnect() give you precise control over when a Combine pipeline starts emitting values. Use connect() when you need explicit timing and coordination, and autoconnect() when you prefer simplicity and immediate start—especially with timers or other periodic publishers. Understanding these tools lets you manage resource usage and data flow more effectively in your Swift applications.

Back to Blog

Related posts

Read more »

Combine #13: Manejo de Recursos

share y multicast_: share La mayoría de los Publishers de Combine son struct que solo describen un pipeline, sin guardar un estado compartido. No se crea una i...

Swift #28: Foundation

markdown !David Goyeshttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fu...