Introduction to Combine

Reviewed & published by Brayan K

Combine is Apple's framework for working with values that arrive over time. In this lesson you'll meet publishers, subscribers, @Published, sink, and AnyCancellable — and learn when Combine beats plain async/await.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

📊 Combine Building Blocks

PieceRoleExample
PublisherEmits values over timeJust(5)
SubscriberReceives values.sink { … }
@PublishedProperty to publisher@Published var n = 0
AnyCancellableHolds a subscription.store(in: &bag)

1️⃣ Publishers and Subscribers

A publisher produces a stream of values; a subscriber consumes them. The simplest subscriber is sink, which runs a closure for each value. Many everyday types become publishers — even arrays.

import Combine

// A Publisher emits values over time. Just emits ONE value then finishes.
let single = Just("Hello")

// A subscriber listens. sink gives you a closure for each received value.
let token = single.sink { value in
    print("Received: \(value)")
}
// Received: Hello

// Sequences and arrays also become publishers.
let numbers = [1, 2, 3].publisher
let token2 = numbers.sink { print("n = \(n)") }
print("done")

2️⃣ @Published — Properties as Streams

Inside an ObservableObject, marking a property with @Published creates a publisher you reach with the $ prefix. Every change is broadcast to subscribers — this is exactly how SwiftUI views stay in sync.

import Combine
import Foundation

// @Published exposes a publisher for a property. Other code can subscribe
// to changes. This is the engine behind SwiftUI's ObservableObject.
final class Clock: ObservableObject {
    @Published var seconds = 0
}

let clock = Clock()

// .$seconds is the publisher created by @Published. sink subscribes to it.
let token = clock.$seconds.sink { value in
    print("seconds is now \(value)")
}

clock.seconds = 1   // prints "seconds is now 1"
clock.seconds = 2   // prints "seconds is now 2"

Your turn. Fill in the two blanks to turn an array into a publisher and subscribe to it.

// 🎯 YOUR TURN — replace each ___ then run it in SwiftFiddle.
import Combine

let greetings = ["hi", "hello", "hey"].___   // 👉 turn the array into a publisher

// Subscribe with sink and print each value.
let token = greetings.___ { value in        // 👉 the closure-subscriber operator
    print("Got: \(value)")
}

// ✅ Expected output:
//    Got: hi
//    Got: hello
//    Got: hey

3️⃣ AnyCancellable — Keeping It Alive

A subscription stays active only as long as you hold its AnyCancellable. The common pattern is a Set<AnyCancellable> property plus .store(in:), so every subscription lives as long as the owning object.

import Combine
import Foundation

final class ViewModel {
    // Store subscriptions so they stay alive while the object lives.
    private var cancellables = Set<AnyCancellable>()

    let subject = PassthroughSubject<Int, Never>()

    init() {
        subject
            .sink { value in print("value: \(value)") }
            .store(in: &cancellables)   // retains the subscription for us
    }

    func emit() {
        subject.send(10)
        subject.send(20)
    }
}

let vm = ViewModel()
vm.emit()
// value: 10
// value: 20

Now you try. Fill in the collection type and the storing operator.

// 🎯 YOUR TURN — keep the subscription alive.
import Combine

var bag = ___<AnyCancellable>()      // 👉 the collection type that holds cancellables

let subject = PassthroughSubject<String, Never>()
subject
    .sink { print("event: \(value)") }
    .___(in: &bag)                   // 👉 the operator that stores the cancellable

subject.send("ready")

// ✅ Expected output:
//    event: ready

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskCodeMeaning
One-value publisherJust(5)emit 5, finish
Array publisher[1,2].publisheremit each
Published property@Published var n = 0stream of changes
Subscribe.sink { v in … }closure subscriber
Retain.store(in: &bag)keep alive

Mini-Challenge: Score Tracker

Build a small ObservableObject and subscribe to its published property. Run it and check your output against the comments.

// 🎯 MINI-CHALLENGE: A tiny score tracker
// 1. Make a class 'Game' that conforms to ObservableObject.
// 2. Give it a @Published var 'score' starting at 0.
// 3. Create a Game, then subscribe to its $score publisher with sink and
//    store the AnyCancellable in a Set<AnyCancellable>.
// 4. Set score to 5, then 10, and confirm the subscriber prints each value.
//
// ✅ Example output:
//    score: 0
//    score: 5
//    score: 10

// your code here

🎉 Lesson Complete!

Practice quiz

What does a Combine Publisher represent?

  • A one-time function call
  • A source that emits values over time
  • A UI view
  • A database table

Answer: A source that emits values over time. A Publisher emits a sequence of values (and optionally a completion) over time.

What role does a Subscriber play?

  • It emits values
  • It receives values from a publisher
  • It stores values on disk
  • It cancels the app

Answer: It receives values from a publisher. A Subscriber receives and reacts to the values a publisher emits.

Which property wrapper turns a property into a publisher inside an ObservableObject?

  • @State
  • @Binding
  • @Published
  • @Environment

Answer: @Published. @Published automatically publishes changes to the wrapped property.

What does the sink operator do?

  • Sorts values
  • Attaches a closure-based subscriber to receive values
  • Filters values
  • Deletes the publisher

Answer: Attaches a closure-based subscriber to receive values. sink attaches a subscriber using closures for received values and completion.

Why must you store the result of sink in an AnyCancellable?

  • For logging
  • It is required by SwiftUI
  • To speed up emissions
  • To keep the subscription alive; if deallocated it cancels

Answer: To keep the subscription alive; if deallocated it cancels. When the AnyCancellable is deallocated the subscription is cancelled, so you must retain it.

A Publisher's two generic types describe its...

  • Output value and Failure error
  • Input and View
  • Key and Value
  • Start and End

Answer: Output value and Failure error. Publisher<Output, Failure> declares the value type and the error type.

Which Failure type means a publisher can never emit an error?

  • Error
  • Any
  • Never
  • Void

Answer: Never. A Failure of Never signals the publisher cannot fail.

Just(5) is an example of what?

  • An operator
  • A publisher that emits one value then completes
  • A subscriber
  • A cancellable

Answer: A publisher that emits one value then completes. Just emits a single value and then finishes.

Combine is generally a good fit when you need to...

  • Run a single async network call
  • Compose ongoing streams of events declaratively
  • Store data in Core Data
  • Render a static view

Answer: Compose ongoing streams of events declaratively. Combine shines for composing continuous streams of events over time.

For a single one-shot asynchronous task, Apple often recommends...

  • Combine over async/await
  • GCD only
  • async/await instead of Combine
  • Never using either

Answer: async/await instead of Combine. async/await is usually simpler for one-shot async work; Combine suits ongoing streams.

Continue this course

Frequently asked questions

What is the difference between a Publisher and a Subscriber?

A Publisher is a source that emits a stream of values (and optionally a completion or failure) over time. A Subscriber attaches to a publisher and reacts to those values. The sink and assign operators create built-in subscribers for you. Publishers describe what data flows; subscribers describe what to do with it.

What does @Published do?

@Published is a property wrapper you use inside an ObservableObject. It automatically creates a publisher (accessed with the $ prefix, e.g. object.$value) that emits the property's new value every time it changes. SwiftUI relies on this so views refresh when an observed object's published properties change.

Why do I need to keep an AnyCancellable around?

sink and assign return an AnyCancellable that represents the live subscription. When that token is deallocated, Combine cancels the subscription and you stop receiving values. Storing it (often in a Set<AnyCancellable> property via .store(in:)) keeps the subscription alive for as long as the owning object exists.

When should I use Combine versus async/await?

Use async/await for one-shot asynchronous work like a single network request — it reads like straight-line code and is simpler. Reach for Combine when you need to compose ongoing streams of events declaratively: debouncing search text, merging multiple sources, or reacting continuously to property changes. Combine excels at the 'values over time' problem.

What do the two generic types on a Publisher mean?

A Publisher has the form Publisher<Output, Failure>. Output is the type of value it emits, and Failure is the type of error it can fail with. If a publisher can never produce an error, its Failure type is Never, which lets you use simpler subscribers that don't handle errors.