iOS

Basic usage guide for Combine

Author : Riven
Published Time : 2025-11-06

Basic usage guide for Combine

Combine is a responsive programming framework launched by Apple in 2019 for handling value streams that change over time. The following are the basic concepts and usage methods of Combine.

core concept

1. Publisher

Source of value generation

Can send 0 or more values

May end with completion or error

2. The subscriber

Receive values from Publisher

Can control the demand for data flow

3. Operator

Convert, filter, and combine values from Publisher

Basic usage examples

Create a simple Publisher

import Combine

// 1. Just - Send a single value and then complete
let justPublisher = Just("Hello, World!")

// 2. Sequence - Sending values in the sequence
let sequencePublisher = [1, 2, 3, 4, 5].publisher

// 3. Future - The result of asynchronous operation
func fetchData() -> Future<String, Error> {
    return Future { promise in
        // Simulate asynchronous operations
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            promise(.success("Data fetched"))
        }
    }
}

// 4. @Published 属性包装器
class DataModel {
    @Published var name: String = "Initial"
}

Subscribe to Publisher

// Use Sink Subscription
var cancellables = Set<AnyCancellable>()

// Subscribe Just
justPublisher
    .sink { value in
        print("Received value: \(value)")
    }
    .store(in: &cancellables)

// Subscribe to Sequence
sequencePublisher
    .sink(
        receiveCompletion: { completion in
            switch completion {
            case .finished:
                print("Finished successfully")
            case .failure(let error):
                print("Failed with error: \(error)")
            }
        },
        receiveValue: { value in
            print("Received: \(value)")
        }
    )
    .store(in: &cancellables)

Common Operators

// Conversion Operators 
sequencePublisher
    .map { $0 * 2 }                    // Convert each value
    .filter { $0 > 5 }                 // Filter value
    .reduce(0, +)                      // Aggregate value
    .sink { print("Result: \($0)") }
    .store(in: &cancellables)

// Combination operator
let publisher1 = [1, 2, 3].publisher
let publisher2 = ["A", "B", "C"].publisher

Publishers.Zip(publisher1, publisher2)
    .sink { print("Zipped: \($0), \($1)") }
    .store(in: &cancellables)

// error handling
enum MyError: Error {
    case testError
}

Fail(outputType: String.self, failure: MyError.testError)
    .catch { error in
        Just("Recovered from error")
    }
    .sink { print($0) }
    .store(in: &cancellables)

Handling UI updates

import UIKit
import Combine

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var button: UIButton!
    
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupBindings()
    }
    
    private func setupBindings() {
        // Monitor text box changes
        NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: textField)
            .compactMap { ($0.object as? UITextField)?.text }
            .sink { [weak self] text in
                self?.label.text = "You typed: \(text)"
            }
            .store(in: &cancellables)
        
        // Button click event
        button.publisher(for: .touchUpInside)
            .sink { [weak self] _ in
                self?.handleButtonTap()
            }
            .store(in: &cancellables)
    }
    
    private func handleButtonTap() {
        print("Button tapped!")
    }
}

Example of Network Request

struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

class UserService {
    private var cancellables = Set<AnyCancellable>()
    
    func fetchUsers() -> AnyPublisher<[User], Error> {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else {
            return Fail(error: URLError(.badURL))
                .eraseToAnyPublisher()
        }
        
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [User].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
    
    func loadUsers() {
        fetchUsers()
            .receive(on: DispatchQueue.main) // Switch to the main thread
            .sink(
                receiveCompletion: { completion in
                    switch completion {
                    case .finished:
                        print("Request completed")
                    case .failure(let error):
                        print("Error: \(error)")
                    }
                },
                receiveValue: { users in
                    print("Received users: \(users)")
                }
            )
            .store(in: &cancellables)
    }
}

Timer Example

class TimerExample {
    private var cancellables = Set<AnyCancellable>()
    
    func startTimer() {
        Timer.publish(every: 1.0, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] date in
                print("Timer fired at: \(date)")
                self?.handleTimerTick()
            }
            .store(in: &cancellables)
    }
    
    private func handleTimerTick() {
        // Process timer trigger
    }
}

Timer Example

class TimerExample {
    private var cancellables = Set<AnyCancellable>()
    
    func startTimer() {
        Timer.publish(every: 1.0, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] date in
                print("Timer fired at: \(date)")
                self?.handleTimerTick()
            }
            .store(in: &cancellables)
    }
    
    private func handleTimerTick() {
        // Process timer trigger
    }
}

memory management

class MyViewController: UIViewController {
    private var cancellables = Set<AnyCancellable>()
    
    deinit {
        // Automatically cancel all subscriptions
        cancellables.forEach { $0.cancel() }
    }
}

best practices

  • Timely unsubscribe: Use store (in:) to manage the subscription lifecycle
  • Thread switching: Use receive (on:) to process data on the appropriate thread
  • Error handling: Reasonable use of operators such as catch and replaceError
  • Avoid strong reference loops: use [weak self] in closures

These are the basic usage methods of Combine. Combine provides powerful responsive programming capabilities, particularly suitable for handling asynchronous event streams and data binding.