Skip to content

Chaining Publishers With Combine (Swift)

A SwiftUI guide for coding agents. Also covers flatmap publisher chain, switchtolatest, debounce search field, cancel stale request, typeahead search combine, race condition search results.

flatMap chains one async operation onto another's result. switchToLatest does the same but drops results from superseded requests, which is the difference between a correct search screen and one where a slow old query overwrites the fresh results.

flatMap Plus Debounce

@Published exposes each property as a publisher via $. Piping the query into a search request is one flatMap; debounce holds the chain until typing pauses so every keystroke does not become a network call.

final class SearchViewModel: ObservableObject {
  @Published var query: String = ""
  @Published private(set) var repos: [Repo] = []

  private let service = GithubService()

  init() {
    $query
      .debounce(for: 0.5, scheduler: DispatchQueue.main)
      .flatMap {
        self.service.searchPublisher(matching: $0)
          .replaceError(with: [])
      }
      .receive(on: DispatchQueue.main)
      .assign(to: &$repos)
  }
}

The Stale-Result Race

With flatMap, every inner publisher stays alive. Type "swift", then "swiftui": if the first request is slower, its response arrives last and replaces the correct results on screen. flatMap cannot prevent this.

switchToLatest

switchToLatest consumes a publisher of publishers, so map the query into a search publisher instead of flat-mapping it. Each new inner publisher cancels the previous one, and only the latest request's results are delivered.

$query
  .debounce(for: 0.5, scheduler: DispatchQueue.main)
  .map {
    self.service.searchPublisher(matching: $0)
      .replaceError(with: [])
  }
  .switchToLatest()
  .receive(on: DispatchQueue.main)
  .assign(to: &$repos)

Rule of thumb: flatMap for chaining dependent operations whose every result matters; map plus switchToLatest for user-driven work where only the latest response should win.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-chaining-publishers-with-combine-framework-in-swift" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems