Open menu with table of contents iOS Development - Introduction to the Combine Framework
Logo of Stuttgart Media University for light theme Logo of Stuttgart Media University for dark theme
Mobile Application Development 2

iOS Development - Introduction to the Combine Framework

What you will learn:

  • Get a brief overview of the Combine Framework
  • What is Combine & what is it used for?
Stuttgart Media University

1 Agenda

  • What is Combine?
  • Core Concepts of Combine
  • The Publish-Subcriber Pattern
  • Publishers
  • Subscribers
  • The Reactive Flow
  • Combine's Functional Approach
  • Practical Example: Network Request with Combine & SwiftUI Integration

2 What is Combine?

The Combine framework, introduced by Apple with iOS 13, enables declarative Swift code for handling asynchronous and event-based programming using reactive principles. It integrates seamlessly with SwiftUI and provides a unified approach to handle asynchronous events and data streams.

Details on all Apple frameworks can be found in the Apple Developer Documentation

3 Core Concepts of Combine

Publishers and Subscribers:

  • Publisher: Represents a sequence of values over time. It can emit elements, errors, and a completion signal.
  • Subscriber: Listens to a publisher, receives values emitted by the publisher, and can react to those values.

Operators:

  • Combine provides numerous operators (map, filter, flatMap, etc.) that allow transformation, filtering, and combination of values emitted by publishers.

Operators and Pipelines:

  • Publishers can be connected to subscribers via operators to create a pipeline. Data flows through this pipeline from the publisher to the subscriber, potentially undergoing transformation along the way.

Schedulers:

  • Schedulers are responsible for determining the execution context in which code associated with a publisher should run (e.g., main thread, background queue).

4 The Publish-Subscriber Pattern

Also know as the Observer pattern.

center

Publishers are Observables and Subscribers are Observers.

A Publisher exposes values that can change and a Subscriber subscribes to receive those value updates.

5 Publishers

A publisher emits values, errors, and a completion signal (.finished or .failure). It conforms to the Publisher protocol, which defines methods like subscribe, receive(subscriber:), and receive(subscription:).

6 Subscribers

A subscriber subscribes to a publisher using the subscribe(:) method. It conforms to the Subscriber protocol, which defines methods like receive(subscription:), receive(:Input), receive(completion:). When a subscriber subscribes to a publisher, it receives a Subscription object. Subscription manages the interaction between a publisher and a subscriber by controlling the demand for values and handling cancellations.

7 Reactive Flow

Publishers emit values and send them downstream to subscribers. Subscribers can receive these values, apply transformations or filtering using operators, and perform further actions accordingly. Subscribers can request more values using demand from the publisher when needed. This demand-driven approach helps manage backpressure.

8 Combine's Functional Approach

Combine uses functional programming concepts (map, filter, etc.) to manipulate data streams, creating a pipeline that transforms and processes values.

Schedulers for Asynchronous Operations:

Combine leverages schedulers to control when and where various parts of a subscription occur, allowing work to be executed on different queues or threads.

9 Practical Example

This example demonstrates fetching data from an API and displaying it in a SwiftUI view. We’ll use URLSession to perform a network request and Combine to handle the asynchronous data flow.

This example demonstrates a simple use case of Combine with SwiftUI to fetch and display data. It’s a starting point for understanding how to integrate Combine’s reactive data flow with SwiftUI. Adjust the Post model, API endpoint, or UI components as needed for your specific use case.

9.1 Model

Post: A simple Post model struct to represent the data fetched from the API.


import SwiftUI
import Combine

// Model for representing fetched data
struct Post: Decodable {
    let id: Int
    let title: String
    let body: String
}

9.2 View Model

PostViewModel: An ObservableObject that fetches data using Combine. It has a @Published property posts that notifies the view whenever it changes. fetchPosts(): Method in PostViewModel that performs a network request using URLSession data task publisher.


// ViewModel to handle networking and data flow
class PostViewModel: ObservableObject {
    // Published property to notify view of data changes
    @Published var posts: [Post] = []
    
    private var cancellable: AnyCancellable?
    
    init() {
        fetchPosts()
    }
    
    func fetchPosts() {
        // Replace with your API endpoint URL
        let urlString = "https://jsonplaceholder.typicode.com/posts"
        
        guard let url = URL(string: urlString) else { return }
        
        // Create a URLSession data task publisher
        cancellable = URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data) // Extract data from response
            .decode(type: [Post].self, decoder: JSONDecoder()) // Decode JSON into array of Posts
            .replaceError(with: []) // Replace errors with an empty array
            .receive(on: DispatchQueue.main) // Receive on main queue to update UI
            .sink(receiveValue: { [weak self] fetchedPosts in
                self?.posts = fetchedPosts // Update posts with fetched data
            })
    }
}

10 SwiftUI View

ContentView: SwiftUI view that observes changes in PostViewModel and displays fetched data in a List.

// SwiftUI ContentView displaying fetched data
struct ContentView: View {
    @ObservedObject var viewModel = PostViewModel()
    
    var body: some View {
        NavigationView {
            List(viewModel.posts, id: \.id) { post in
                VStack(alignment: .leading) {
                    Text(post.title)
                        .font(.headline)
                    Text(post.body)
                        .font(.body)
                        .foregroundColor(.gray)
                }
            }
            .navigationTitle("Posts")
        }
    }
}

// Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

11 References