Combine — conceitos-chave, Publisher e programação reativa

Autor: IT Sectr Publicado: 2026-05-03 Tempo de leitura: 11 min

Combine é o framework nativo de programação reativa da Apple, apresentado no iOS 13, macOS Catalina, tvOS 13 e watchOS 6. Ele fornece uma API declarativa Swift para processar eventos assíncronos através do padrão Publisher e Subscriber, substituindo delegates, closures e NotificationCenter por uma cadeia unificada. De acordo com a Apple, 2025, o Combine é a base para o SwiftUI e arquiteturas modernas do iOS, trabalhando em estreita colaboração com async/await e Structured Concurrency. O framework foi projetado para composição de operações assíncronas com garantias de segurança de threads.

Principais Conclusões

  • Combine é o framework reativo nativo da Apple com tipos Publisher, Subscriber, Subject e tipos de erro através de Failure
  • Publisher emite eventos ao longo do tempo: valores Output, pode concluir com sucesso ou erro Failure
  • Subscriber recebe eventos do Publisher e pode solicitar um número específico de itens através de Demand
  • Operadores map, flatMap, filter, combineLatest, zip, debounce transformam fluxos sem condições de corrida
  • SwiftUI usa Combine através de @Published, ObservableObject e @StateObject para atualizações reativas da interface

O que é o Combine?

Combine é um framework de programação reativa declarativa integrado ao SDK da Apple. Ele implementa o padrão Reactive Streams: Publisher produz valores, Subscriber os consome e os operadores transformam o fluxo entre eles. O Combine resolve o problema de callbacks e delegates fornecendo um modelo de composição unificado para qualquer evento assíncrono — desde respostas de rede até mudanças na interface do usuário.

Antes do Combine, os desenvolvedores iOS usavam bibliotecas de terceiros, principalmente o RxSwift. A Apple criou o Combine como uma alternativa nativa com integração profunda no ecossistema: o framework suporta Objective-C através de pontes @objc, funciona com KVO (Key-Value Observing via NSObject.keyValuePublisher) e NotificationCenter, e também é a base para o SwiftUI. Todos os componentes UIKit publicados no SwiftUI usam o Combine internamente para atualizações de visualização.

O Combine é projetado com a Swift Concurrency em mente: a partir do iOS 15, um Publisher pode ser convertido em AsyncSequence através de .values e usado em loops for-await-in. A conversão reversa de funções async para Publisher é feita através de Future. De acordo com a Apple WWDC 2024, o Combine continua sendo o framework recomendado para manipulação de dados em streaming em aplicativos UIKit, apesar da introdução de async/await para chamadas assíncronas únicas.

Conceitos Fundamentais do Combine

O Combine é baseado em três protocolos: Publisher (emite valores do tipo Output, pode falhar com um erro do tipo Failure), Subscriber (recebe valores, gerencia Demand — o número de elementos solicitados), Subscription (representa a conexão Publisher-Subscriber com capacidade de cancelamento). O canal de dados é inicializado ao assinar e termina ao cancelar, completar ou com erro. Demand é um conceito único do Combine: o Subscriber informa ao Publisher quantos elementos está pronto para processar, implementando backpressure no nível do protocolo.

Publisher e Subscriber: Arquitetura de Fluxo Reativo

Publisher é um protocolo com dois tipos associados: Output (o tipo de valores emitidos) e Failure (o tipo de erro que implementa Error). Se o fluxo não pode falhar, Failure é especificado como Never — isso garante ao Subscriber que onReceive será chamado apenas com Output. Publishers integrados incluem Just (valor único), Sequence (array), URLSession.DataTaskPublisher (requisição de rede), NotificationCenter.Publisher e o property wrapper @Published.

swift
import Combine

// Criando Publisher a partir de uma sequência
let publisher = [1, 2, 3, 4, 5].publisher

// Criando Subscriber com tratamento de valores
class PrintSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never

    func receive(subscription: Subscription) {
        subscription.request(.unlimited)
    }

    func receive(_ input: Int) -> Subscribers.Demand {
        print("Received: \(input)")
        return .unlimited
    }

    func receive(completion: Subscribers.Completion<Never>) {
        print("Concluído")
    }
}

publisher.subscribe(PrintSubscriber())

Subscription e Demand

Subscription é um protocolo que representa a conexão ativa entre Publisher e Subscriber. O Subscriber recebe Subscription no método receive(subscription:) e chama request(_:) para especificar Demand: .unlimited (todos os valores), .max(N) (quantidade limitada) ou .none (pausa). O Demand pode mudar dinamicamente — o Subscriber pode aumentar ou diminuir o número de elementos solicitados durante o recebimento de dados. Isso fornece backpressure sem buffer no lado do Publisher.

Subject e CurrentValueSubject

Subject é um tipo que combina Publisher e Subscriber. Um Subject pode ser usado como Publisher (assinantes assinam ele) e simultaneamente como Subscriber (valores são enviados para ele). O Combine fornece dois tipos de Subject: PassthroughSubject (não armazena estado, apenas passa novos valores) e CurrentValueSubject (armazena o valor atual e o passa para novos assinantes). O Subject é necessário para integrar código imperativo em cadeias reativas do Combine.

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

// Assinando como Publisher
let cancellable = subject
    .map { $0.uppercased() }
    .sink { print($0) }

// Enviando valores como Subscriber
subject.send("hello")  // Imprime "HELLO"
subject.send("world")  // Imprime "WORLD"

CurrentValueSubject difere do PassthroughSubject por ter um valor inicial e uma propriedade value: um assinante recebe imediatamente o valor atual ao assinar, e então todas as atualizações subsequentes. CurrentValueSubject.value é legível e gravável — alterar value envia automaticamente o novo valor para todos os assinantes. Isso torna o CurrentValueSubject uma escolha ideal para representar estado na arquitetura MVVM: ViewModel publica CurrentValueSubject, View assina com mudanças através de sink.

Ambos os Subjects podem encerrar o fluxo chamando send(completion: .finished) ou send(completion: .failure(error)). Após o encerramento, o Subject para de aceitar e emitir eventos. Para fluxos de longa duração que não devem ser encerrados (por exemplo, eventos de UI), recomenda-se usar PassthroughSubject com Never Failure para evitar chamar acidentalmente send(completion:).

Operadores de Transformação e Combinação

Operadores do Combine são métodos do Publisher que retornam um novo Publisher. Cada operador cria um novo objeto que assina o Publisher upstream e emite valores transformados downstream. Como Publisher é um tipo genérico, os operadores mantêm tipagem estrita: map transforma Output<A> em Output<B>, tryMap adiciona a possibilidade de erro. O Combine contém cerca de 100 operadores integrados.

CategoriaOperadorPropósito
Transformaçãomap / tryMap / flatMapTransformar valores ou fluxos
Filtragemfilter / compactMap / removeDuplicatesSelecionar ou limpar valores
CombinaçãocombineLatest / zip / mergeCombinar vários Publishers
Controle de Tempodebounce / throttle / delayAtrasar e espaçar eventos
Tratamento de Erroscatch / retry / replaceErrorRecuperação após Failure
Gerenciamento de Demandbuffer / collectAgrupamento ou buffer

flatMap no Combine tem uma diferença importante em relação à versão do RxSwift: ele aceita uma closure que retorna um Publisher com o mesmo tipo Failure e achata o Publisher aninhado no fluxo principal. flatMap com maxPublishers: .max(1) se comporta como switchMap — cancela o Publisher aninhado anterior quando um novo valor chega. Isso é crítico para cenários de busca: quando um novo caractere é digitado, a requisição HTTP anterior é cancelada automaticamente.

swift
// Busca com debounce e cancelamento da requisição anterior
searchTextField.textPublisher
    .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
    .removeDuplicates()
    .flatMap(maxPublishers: .max(1)) { query in
        apiService.searchPublisher(query)
            .catch { _ in Just([]) }
    }
    .receive(on: DispatchQueue.main)
    .sink { results in
        self.tableView.reloadData()
    }
    .store(in: &cancellables)

Operadores de combinação — combineLatest e zip — funcionam de forma similar ao RxSwift: combineLatest emite uma tupla dos valores mais recentes de todos os Publishers quando qualquer um muda; zip combina valores por índice. merge combina Publishers do mesmo tipo em um único fluxo, a preservação de ordem não é garantida. O Combine também tem select — um operador raro que escolhe o primeiro Publisher a completar entre vários, e share — multicast de um fluxo para múltiplos assinantes sem re-execução.

Schedulers e Gerenciamento de Threads

Scheduler no Combine é um protocolo que define o contexto de execução para operadores. Ao contrário do RxSwift com seus mais de 5 Schedulers integrados, o Combine usa mecanismos existentes da Apple: DispatchQueue, RunLoop e OperationQueue. Cada um desses tipos está em conformidade com o protocolo Scheduler, permitindo que sejam passados diretamente para receive(on:) e subscribe(on:) sem adaptadores adicionais.

receive(on:) muda o downstream para o Scheduler especificado — equivalente a observeOn no RxSwift. Todos os operadores após receive(on:) executam no Scheduler especificado. subscribe(on:) muda o upstream — afeta a execução do Publisher. Um padrão típico: subscribe(on: DispatchQueue.global()) para trabalho em segundo plano e receive(on: DispatchQueue.main) para atualizações de UI. No SwiftUI, ao usar .onReceive, a vinculação embutida à thread principal não é necessária, mas para sink, receive(on:).main explícito é recomendado.

swift
// Carregamento em segundo plano + UI na thread principal
URLSession.shared.dataTaskPublisher(for: url)
    .subscribe(on: DispatchQueue.global(qos: .background))
    .tryMap { data, response -> Data in
        guard let http = response as? HTTPURLResponse,
              http.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
    .receive(on: DispatchQueue.main)
    .decode(type: User.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { print($0) },
         receiveValue: { self.nameLabel.text = $0.name })
    .store(in: &cancellables)

RunLoop.main é uma alternativa ao DispatchQueue.main para operações de UI. A diferença é que o RunLoop.main está vinculado ao loop de eventos atual do aplicativo, enquanto o DispatchQueue.main está vinculado à fila global da thread principal. Para UIKit, recomenda-se DispatchQueue.main; para SwiftUI — RunLoop.main. ImmediateWhenScheduler executa operações sincronamente na thread atual — usado por padrão para testes e Publishers simples.

Combine e SwiftUI: Integração através de ObservableObject

ObservableObject é um protocolo do SwiftUI para objetos que publicam mudanças. Uma classe que implementa ObservableObject pode usar o property wrapper @Published para propriedades cujas mudanças notificam automaticamente o SwiftUI sobre a necessidade de redesenhar. Internamente, @Published cria um Publisher que notifica o Publisher objectWillChange quando wrappedValue muda. O SwiftUI assina o objectWillChange através de @StateObject, @ObservedObject ou @EnvironmentObject.

@Published e @StateObject

@Published é a forma mais comum de integrar o Combine no SwiftUI. Quando o valor de uma propriedade @Published muda, o SwiftUI atualiza todas as Views que usam esse objeto. @StateObject cria uma instância de ObservableObject e assina suas mudanças. Uma View criada com @StateObject é redesenhada automaticamente quando propriedades @Published mudam. Se o objeto precisar ser compartilhado entre várias Views, @ObservedObject ou @EnvironmentObject são usados.

swift
class UserViewModel: ObservableObject {
    @Published var name: String = ""
    @Published var age: Int = 0
    private var cancellables = Set<AnyCancellable>()

    init() {
        $name
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { [weak self] newName in
                AnalyticsService.logNameChange(newName)
            }
            .store(in: &cancellables)
    }
}

struct UserView: View {
    @StateObject var viewModel = UserViewModel()

    var body: some View {
        TextField("Name", text: $viewModel.name)
    }
}

AnyCancellable é um wrapper de apagamento de tipo para Cancellable que armazena um token de cancelamento de assinatura. Set<AnyCancellable> gerencia o ciclo de vida das assinaturas: quando o proprietário é desinicializado, todos os Cancellable são cancelados automaticamente. Em projetos SwiftUI, Set<AnyCancellable> é declarado na classe ObservableObject, e as assinaturas são adicionadas através de .store(in: &cancellables). Para UIKit, os mesmos mecanismos são usados com armazenamento em UIViewController através de &cancellables ou chamadas manuais de cancel().

Combine vs RxSwift

Combine e RxSwift resolvem as mesmas tarefas de programação reativa, mas têm diferenças arquitetônicas fundamentais. O Combine faz parte do SDK da Apple com compatibilidade reversa até o iOS 13, o RxSwift é uma biblioteca de terceiros com suporte ao iOS 8+. O Combine usa tipagem estrita de erros através de Failure genérico, o RxSwift usa um único tipo Error. O Combine é integrado ao SwiftUI no nível da plataforma, o RxSwift requer RxCocoa para extensões de UI.

A escolha entre Combine e RxSwift depende dos requisitos do projeto. Se a versão mínima do iOS for >= 13 e o projeto usar SwiftUI — o Combine é a escolha natural graças à integração embutida e à ausência de dependências adicionais. Se o projeto suporta iOS 11-12, contém uma base de código RxSwift existente ou requer operadores específicos disponíveis apenas no RxSwift (por exemplo, Observable.from(path:)), — o RxSwift continua sendo uma solução válida.

CaracterísticaCombineRxSwift
DesenvolvedorApple (integrado ao SDK)ReactiveX (comunidade)
Versão iOSiOS 13+iOS 8+
Tipo de ErroFailure genérico (Never para UI)Error (qualquer)
Integração UI@Published + SwiftUIRxCocoa + UIKit
Operadores~100 integrados400+ operadores
Swift ConcurrencyAtravés de .values (sequência async)Através de biblioteca ponte

Perguntas Frequentes

Qual a diferença entre PassthroughSubject e CurrentValueSubject?

PassthroughSubject não armazena estado — um assinante recebe apenas eventos enviados após a assinatura. CurrentValueSubject armazena o valor atual e o passa para cada novo assinante imediatamente ao assinar. CurrentValueSubject é adequado para representar estado (por exemplo, isLoggedIn).

Como cancelar uma assinatura no Combine?

Uma assinatura retorna AnyCancellable, que é cancelado ao chamar cancel() ou na desinicialização. Para gerenciamento em grupo, use Set<AnyCancellable> — todas as assinaturas são canceladas quando o conjunto é limpo. Isso é análogo ao DisposeBag no RxSwift.

Preciso aprender Combine depois de async/await?

Sim, o Combine continua relevante para dados em streaming: eventos de UI, debounce, combineLatest, WebSocket. async/await é conveniente para requisições únicas, Combine — para fluxos contínuos ou múltiplos. Ambos os frameworks são complementares — Publisher pode ser convertido em AsyncSequence.

Como usar Combine com UIKit?

UIKit não tem Publishers integrados, mas a Apple fornece extensões: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. Para eventos de UI personalizados, PassthroughSubject ou @IBAction encapsulados em um Publisher através de Future ou Subject são usados.

O que é backpressure no Combine?

Backpressure é um mecanismo para controlar a velocidade do fluxo: o Subscriber informa ao Publisher através de Demand quantos elementos está pronto para processar. Se Demand = .max(1), o Publisher aguarda uma solicitação antes de enviar o próximo valor. Isso evita estouro de buffer quando as velocidades do produtor e consumidor não coincidem.

Resumo

  • Combine é o framework reativo nativo da Apple para iOS 13+ com o padrão Publisher-Subscriber e tipagem estrita de erros
  • Publisher e Subscriber formam um canal de comunicação com gerenciamento de Demand para backpressure
  • Subject (Passthrough e CurrentValue) integram código imperativo em cadeias reativas
  • Operadores map, flatMap, combineLatest, debounce, catch fornecem processamento declarativo de fluxos
  • Schedulers através de DispatchQueue e RunLoop gerenciam threads de execução sem bloquear a UI
  • SwiftUI usa Combine através de @Published, ObservableObject e @StateObject para Views reativas
  • RxSwift continua sendo uma alternativa para projetos com iOS 8+, Combine é a escolha para novos projetos no iOS 13+

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também