← Back to Articles
SwiftUI Combine iOS Development Swift Reactive Programming Mobile Development

SwiftUI Combine Framework Complete Guide: Reactive Programming for Modern iOS Applications

Learn how to use the Combine framework with SwiftUI to build reactive iOS applications. Understand publishers, subscribers, operators, @Published, ObservableObject, asynchronous streams, networking, and best practices.

July 2026
35 min read
Written by Engineering Team

Introduction

Modern iOS applications constantly react to changing data. User input, network responses, database updates, notifications, and system events all represent streams of information that applications need to handle efficiently.

The Combine framework provides Apple developers with a reactive programming model for processing asynchronous events over time.

When combined with SwiftUI, Combine enables powerful state-driven applications where views automatically update when underlying data changes.

Although Swift concurrency with async/await is now preferred for many new asynchronous operations, Combine remains important because many Apple frameworks and existing applications rely on publishers and reactive streams.

SwiftUI Combine reactive data flow
Combine creates reactive pipelines between data sources and SwiftUI views.

What Is Combine Framework?

Combine is Apple’s framework for handling asynchronous events by using publishers that emit values and subscribers that receive those values.

Instead of manually calling callbacks whenever data changes, Combine creates a pipeline where data flows automatically through transformations.

Traditional Callbacks Combine
Manual completion handlers Reactive pipelines
Nested callback code Composable operators
Hard cancellation Built-in subscriptions
Manual state updates Automatic publishing

Core Concepts of Combine

Combine is built around three main concepts: publishers, subscribers, and operators.

Concept Purpose
Publisher Produces values over time
Subscriber Receives values
Operator Transforms data streams

Publishers in Combine

A publisher describes a source of values that can arrive asynchronously.

Publishers can represent network responses, user actions, timers, notifications, or custom events.

PublisherExample.swift
swift
              let publisher =
    Just("Hello Combine")


publisher
    .sink { value in

        print(value)

    }

            

Subscribers

Subscribers consume values emitted by publishers. The most common subscriber in Combine is sink.

SubscriberExample.swift
swift
              publisher
    .sink(
        receiveCompletion: { result in

            print(result)

        },

        receiveValue: { value in

            print(value)

        }
    )

            

AnyCancellable and Memory Management

Combine subscriptions must be stored to remain active. AnyCancellable represents a subscription that can be cancelled manually.

CancellableExample.swift
swift
              class ViewModel {


    private var cancellables =
        Set<AnyCancellable>()


    func subscribe() {


        publisher
            .sink { value in

                print(value)

            }

            .store(
                in: &cancellables
            )

    }

}

            

Combine with ObservableObject

ObservableObject is one of the most common ways Combine integrates with SwiftUI. Objects marked as ObservableObject notify views when published properties change.

UserViewModel.swift
swift
              class UserViewModel:
    ObservableObject {


    @Published
    var username = ""


}

            

@Published Property Wrapper

@Published automatically creates a publisher for a property. Whenever the value changes, subscribers receive a new value.

PublishedExample.swift
swift
              class SettingsViewModel:
    ObservableObject {


    @Published
    var isDarkMode = false


}

            

Using Combine in SwiftUI Views

SwiftUI observes ObservableObject instances and automatically refreshes the UI when published properties change.

ContentView.swift
swift
              struct ContentView:
    View {


    @StateObject
    private var viewModel =
        UserViewModel()



    var body: some View {

        Text(
            viewModel.username
        )

    }

}

            

Combine Operators

Operators transform publisher output and allow developers to create complex asynchronous workflows.

  • map

  • filter

  • combineLatest

  • merge

  • debounce

  • removeDuplicates

  • flatMap

Transforming Data with map Operator

The map operator transforms values emitted by a publisher. It is commonly used to convert API models into UI models or modify data before displaying it.

MapOperator.swift
swift
              let numbers =
    [1, 2, 3]
        .publisher


numbers
    .map {
        $0 * 2
    }

    .sink { value in

        print(value)

    }

            

Filtering Data with filter

The filter operator allows only values that match a specific condition to continue through the publisher pipeline.

FilterOperator.swift
swift
              searchResults
    .filter {

        $0.isActive

    }

    .sink { item in

        print(item)

    }

            

Combining Multiple Publishers

Applications often need to react to multiple data sources at the same time. Combine provides operators for merging and combining publishers.

CombineLatest.swift
swift
              Publishers
    .CombineLatest(
        usernamePublisher,
        passwordPublisher
    )

    .map {

        username,
        password in


        return !username.isEmpty
            && !password.isEmpty

    }

    .sink { valid in

        print(valid)

    }

            

Debounce for Search Fields

Debounce is one of the most useful Combine operators in SwiftUI applications. It delays events until the user stops producing new values.

A common example is a search field where API requests should only happen after the user finishes typing.

SearchViewModel.swift
swift
              class SearchViewModel:
    ObservableObject {


    @Published
    var query = ""


    private var cancellables =
        Set<AnyCancellable>()



    init() {


        $query

        .debounce(
            for: .milliseconds(500),
            scheduler: DispatchQueue.main
        )

        .sink { value in

            self.search(value)

        }

        .store(
            in: &cancellables
        )

    }

}

            

Networking with Combine and URLSession

Combine integrates directly with URLSession, allowing developers to create reactive networking pipelines.

ApiService.swift
swift
              class ApiService {


    func fetchUsers()

    -> AnyPublisher<[User], Error> {


        URLSession.shared
            .dataTaskPublisher(
                for:
                URL(
                    string:
                    "https://api.example.com/users"
                )!
            )

            .map {
                $0.data
            }

            .decode(
                type:
                [User].self,
                decoder:
                JSONDecoder()
            )

            .eraseToAnyPublisher()

    }

}

            

Repository Pattern with Combine

Combine works well with the repository pattern because repositories can expose publishers while hiding networking details from ViewModels.

UserRepository.swift
swift
              protocol UserRepository {


    func users()

    -> AnyPublisher<
        [User],
        Error
    >

}

            
UserRepositoryImpl.swift
swift
              class UserRepositoryImpl:
    UserRepository {


    private let api:
        ApiService



    init(
        api: ApiService
    ) {

        self.api = api

    }



    func users()

    -> AnyPublisher<
        [User],
        Error
    > {


        api.fetchUsers()

    }

}

            

Combine with MVVM Architecture

A common SwiftUI architecture uses Combine inside ViewModels to transform repository data into UI state.

UsersViewModel.swift
swift
              class UsersViewModel:
    ObservableObject {


    @Published
    var users:
    [User] = []



    private let repository:
    UserRepository



    private var cancellables =
        Set<AnyCancellable>()



    init(
        repository:
        UserRepository
    ) {

        self.repository =
        repository


    }



    func loadUsers() {


        repository
            .users()

            .receive(
                on:
                DispatchQueue.main
            )

            .sink(

                receiveCompletion:
                { _ in },

                receiveValue:
                {
                    users in

                    self.users =
                    users

                }

            )

            .store(
                in:
                &cancellables
            )

    }

}

            

Subjects in Combine

Subjects are special publishers that allow developers to manually send values. They are useful for communication between different parts of an application.

  • PassthroughSubject

  • CurrentValueSubject

PassthroughSubject Example

PassthroughSubject does not store the current value. It only sends new events to active subscribers.

SubjectExample.swift
swift
              let subject =
    PassthroughSubject<String, Never>()



subject
    .sink { value in

        print(value)

    }



subject.send(
    "New Message"
)

            

CurrentValueSubject Example

CurrentValueSubject stores the latest value and immediately sends it to new subscribers.

CurrentValueSubject.swift
swift
              let subject =
    CurrentValueSubject<Int, Never>(
        0
    )



subject.send(10)



subject
    .sink { value in

        print(value)

    }

            

Timer Publishers

Combine can create publishers for timers. This is useful for countdowns, clocks, progress indicators, and periodic updates.

TimerPublisher.swift
swift
              Timer
    .publish(
        every:
        1,
        on:
        .main,
        in:
        .common
    )

    .autoconnect()

    .sink { date in

        print(date)

    }

            

NotificationCenter with Combine

Combine provides publishers for system notifications, allowing applications to react to events without traditional observers.

NotificationPublisher.swift
swift
              NotificationCenter
    .default
    .publisher(
        for:
        UIApplication
        .didEnterBackgroundNotification
    )

    .sink { _ in

        print(
            "App entered background"
        )

    }

            

Combine vs Async/Await

Swift concurrency introduced async/await, which provides a simpler approach for handling many asynchronous operations. However, Combine remains useful for continuous data streams and reactive pipelines.

Combine Async/Await
Reactive streams One-time async operations
Multiple emitted values Single returned value
Powerful operators Simpler syntax
Complex event processing API calls and tasks

For example, downloading a user profile usually fits async/await, while observing a search field, database updates, or application events often fits Combine.

Using Combine and Async/Await Together

Modern Swift applications can use both approaches together. Combine can handle continuous streams while async/await handles individual asynchronous operations.

ModernHybridApproach.swift
swift
              func loadProfile()
async throws -> User {


    let user =
        try await api
        .fetchUser()


    return user

}

            

A ViewModel can expose the final state using @Published while internally using async/await for data loading.

Testing Combine Pipelines

Combine pipelines should be tested to ensure publishers emit expected values, handle failures correctly, and complete properly.

CombineTest.swift
swift
              func testPublisher() {


    let expectation =
        XCTestExpectation()



    publisher

    .sink { value in


        XCTAssertEqual(
            value,
            expected
        )


        expectation
        .fulfill()

    }

}

            

Testing ViewModels with Combine

ViewModels using Combine should be tested by verifying state changes after receiving publisher values.

  • Initial state

  • Loading state

  • Success state

  • Error handling

  • Cancellation behavior

Combine Memory Management

Combine subscriptions create references between publishers and subscribers. Incorrect handling can lead to memory leaks.

  • Store AnyCancellable references

  • Use weak self inside closures

  • Cancel subscriptions when no longer needed

  • Avoid strong reference cycles

WeakSelfExample.swift
swift
              publisher

    .sink { [weak self] value in


        self?
        .update(value)


    }

    .store(
        in:
        &cancellables
    )

            

Combine Best Practices

  • Keep publishers inside service and repository layers

  • Expose simple state to SwiftUI views

  • Avoid large Combine chains

  • Use meaningful operators

  • Handle errors explicitly

  • Cancel unused subscriptions

💡 Architecture Tip

SwiftUI views should not contain Combine logic. Keep reactive pipelines inside ViewModels or dedicated services.

Common Combine Mistakes

  • Creating subscriptions inside Views

  • Forgetting to store AnyCancellable

  • Ignoring completion events

  • Using Combine when async/await is simpler

  • Creating complicated publisher chains

  • Not handling errors

Production SwiftUI Combine Architecture

SwiftUI Combine MVVM architecture
Typical production architecture using SwiftUI and Combine.
Project Structure
text
              App/


Views/

    ContentView.swift


ViewModels/

    UserViewModel.swift


Repositories/

    UserRepository.swift


Services/

    ApiService.swift


Models/

    User.swift

            

When Should You Use Combine?

Combine is still valuable in many SwiftUI applications, especially when dealing with continuous streams of data.

  • Real-time updates

  • Search suggestions

  • Form validation

  • Notifications

  • Timers

  • Reactive UI state

  • Existing Apple framework integrations

When Should You Prefer Async/Await?

  • REST API requests

  • File operations

  • Single database operations

  • Background tasks

  • Sequential asynchronous workflows

Frequently Asked Questions

Is Combine still relevant with async/await?
Yes. Async/await simplifies many asynchronous tasks, but Combine remains powerful for reactive streams and event-driven applications.
Should every SwiftUI app use Combine?
No. Small applications may work perfectly with Swift concurrency and simple state management. Combine is useful when applications require reactive pipelines.
Is @Published part of Combine?
Yes. @Published is provided by Combine and creates a publisher that emits changes whenever the property value updates.
Can Combine work with SwiftUI?
Yes. SwiftUI was designed to work with Combine concepts such as ObservableObject and published state.
Should I learn Combine before async/await?
Learning both is valuable. Async/await is easier for beginners, while Combine teaches reactive programming concepts used throughout Apple frameworks.

Conclusion

The Combine framework remains an important technology in the Swift ecosystem. Together with SwiftUI, it provides a powerful reactive programming model for building responsive applications. Understanding publishers, subscribers, operators, and state management allows developers to create cleaner architectures and handle complex asynchronous workflows effectively.

We use cookies to improve your experience.