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
- • What a Publisher and Subscriber are, and how data flows between them
- • Use @Published to turn a property into a stream of changes
- • Attach a subscriber with sink
- • Keep subscriptions alive with AnyCancellable and store(in:)
- • Read a publisher's Output and Failure types
- • Decide when to reach for Combine vs async/await
📊 Combine Building Blocks
| Piece | Role | Example |
|---|---|---|
| Publisher | Emits values over time | Just(5) |
| Subscriber | Receives values | .sink { … } |
| @Published | Property to publisher | @Published var n = 0 |
| AnyCancellable | Holds 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: hey3️⃣ 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: 20Now 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: readyPro Tips
- 💡 Always store your cancellables. A forgotten subscription is silently cancelled the moment its token goes out of scope.
- 💡 Use Never as Failure when a stream can't error — it lets you use the simpler single-closure sink.
- 💡 Combine vs async/await: one-shot call → async/await; ongoing stream → Combine.
- 💡 Think in streams. Ask "what values flow over time?" before writing pipelines.
Common Errors (and the fix)
- Subscriber never fires — you didn't keep the AnyCancellable. Store it with .store(in:&bag).
- "Cannot convert value of type 'Published<Int>.Publisher'" — subscribe to the $ projected value, e.g. object.$count, not the raw property.
- Compiler can't find Combine types — add import Combine at the top of the file.
- Error-handling closure required — your publisher's Failure isn't Never; use the two-closure sink(receiveCompletion:receiveValue:).
📋 Quick Reference
| Task | Code | Meaning |
|---|---|---|
| One-value publisher | Just(5) | emit 5, finish |
| Array publisher | [1,2].publisher | emit each |
| Published property | @Published var n = 0 | stream 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!
- ✅ Publishers emit values over time; subscribers receive them
- ✅ @Published turns a property into a stream of changes
- ✅ sink attaches a closure-based subscriber
- ✅ Hold the AnyCancellable (often in a Set) to keep it alive
- ✅ Use Combine for ongoing streams; async/await for one-shot work
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
- Previous: Custom View Modifiers & ViewBuilder
- Next: Combine Operators & Pipelines — map, filter, debounce, combineLatest and receive(on:)
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.