Combine — adalah framework pemrograman reaktif native dari Apple, diperkenalkan di iOS 13, macOS Catalina, tvOS 13 dan watchOS 6. Framework ini menyediakan Swift API deklaratif untuk memproses peristiwa asinkron melalui pola Publisher dan Subscriber, menggantikan delegasi, closure dan NotificationCenter dengan rantai terpadu. Menurut Apple, 2025, Combine adalah fondasi untuk SwiftUI dan arsitektur iOS modern, bekerja erat dengan async/await dan Structured Concurrency. Framework ini dirancang untuk komposisi operasi asinkron dengan jaminan keamanan thread.
Poin utama
Combine — adalah framework pemrograman reaktif deklaratif yang tertanam di Apple SDK. Ini mengimplementasikan pola Reactive Streams: Publisher menghasilkan nilai, Subscriber mengonsumsinya, dan operator mentransformasi aliran di antara mereka. Combine memecahkan masalah callback dan delegasi dengan menyediakan model komposisi terpadu untuk peristiwa asinkron apa pun — dari respons jaringan hingga perubahan UI.
Sebelum munculnya Combine, pengembang iOS menggunakan pustaka pihak ketiga, terutama RxSwift. Apple menciptakan Combine sebagai alternatif native dengan integrasi mendalam ke ekosistem: framework ini mendukung Objective-C melalui jembatan @objc, bekerja dengan KVO (Key-Value Observing melalui NSObject.keyValuePublisher) dan NotificationCenter, dan juga merupakan dasar untuk SwiftUI. Semua komponen UIKit yang dipublikasikan di SwiftUI menggunakan Combine di balik layar untuk memperbarui tampilan.
Combine dirancang dengan mempertimbangkan Swift Concurrency: mulai iOS 15, Publisher dapat diubah menjadi AsyncSequence melalui .values dan digunakan dalam loop for-await-in. Konversi terbalik fungsi async ke Publisher dilakukan melalui Future. Menurut Apple WWDC 2024, Combine tetap menjadi framework yang direkomendasikan untuk memproses data streaming di aplikasi UIKit, meskipun munculnya async/await untuk panggilan asinkron tunggal.
Combine didasarkan pada tiga protokol: Publisher (memancarkan nilai tipe Output, dapat berakhir dengan kesalahan tipe Failure), Subscriber (menerima nilai, mengelola Demand — jumlah elemen yang diminta), Subscription (mewakili koneksi Publisher-Subscriber dengan kemampuan pembatalan). Saluran transmisi data diinisialisasi saat subscribe dan berakhir saat cancel, completion atau error. Demand — konsep unik Combine: Subscriber memberi tahu Publisher berapa banyak elemen yang siap diproses, mengimplementasikan backpressure di tingkat protokol.
Publisher — protokol dengan dua tipe terkait: Output (tipe nilai yang dipancarkan) dan Failure (tipe kesalahan yang mengimplementasikan Error). Jika aliran tidak dapat berakhir dengan kesalahan, Failure ditetapkan sebagai Never — ini menjamin Subscriber bahwa onReceive hanya akan dipanggil dengan Output. Publisher bawaan termasuk Just (nilai tunggal), Sequence (array), URLSession.DataTaskPublisher (permintaan jaringan), NotificationCenter.Publisher dan @Published property wrapper.
import Combine
// Membuat Publisher dari urutan
let publisher = [1, 2, 3, 4, 5].publisher
// Membuat Subscriber dengan pemrosesan nilai
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("Selesai")
}
}
publisher.subscribe(PrintSubscriber())
Subscription — adalah protokol yang mewakili koneksi aktif antara Publisher dan Subscriber. Subscriber menerima Subscription dalam metode receive(subscription:) dan memanggil request(_:) untuk menentukan Demand: .unlimited (semua nilai), .max(N) (jumlah terbatas) atau .none (jeda). Demand dapat berubah secara dinamis — Subscriber dapat menambah atau mengurangi jumlah elemen yang diminta selama proses penerimaan data. Ini memastikan backpressure tanpa buffering di sisi Publisher.
Subject — adalah tipe yang menggabungkan Publisher dan Subscriber. Subject dapat digunakan sebagai Publisher (dapat berlangganan) dan sekaligus sebagai Subscriber (nilai dapat dikirim ke dalamnya). Combine menyediakan dua jenis Subject: PassthroughSubject (tidak menyimpan status, hanya meneruskan nilai baru) dan CurrentValueSubject (menyimpan nilai saat ini dan meneruskannya ke pelanggan baru). Subject diperlukan untuk integrasi kode imperatif ke dalam rantai reaktif Combine.
let subject = PassthroughSubject<String, Never>()
// Langganan sebagai Publisher
let cancellable = subject
.map { $0.uppercased() }
.sink { print($0) }
// Mengirim nilai sebagai Subscriber
subject.send("hello") // Menampilkan “HELLO”
subject.send("dunia") // Menampilkan “WORLD”
CurrentValueSubject berbeda dari PassthroughSubject dengan adanya nilai awal dan properti value: pelanggan segera menerima nilai saat ini saat berlangganan, kemudian semua pembaruan berikutnya. CurrentValueSubject.value dapat diakses untuk dibaca dan ditulis — perubahan value secara otomatis mengirimkan nilai baru ke semua pelanggan. Ini menjadikan CurrentValueSubject pilihan ideal untuk mewakili status dalam arsitektur MVVM: ViewModel mempublikasikan CurrentValueSubject, View berlangganan dengan perubahan melalui sink.
Kedua Subject memungkinkan penghentian aliran dengan memanggil send(completion: .finished) atau send(completion: .failure(error)). Setelah selesai, Subject berhenti menerima dan meneruskan peristiwa. Untuk aliran berumur panjang yang tidak boleh berakhir (misalnya peristiwa UI), disarankan menggunakan PassthroughSubject dengan Never Failure untuk mengecualikan panggilan send(completion:) yang tidak disengaja.
Operator Combine — adalah metode Publisher yang mengembalikan Publisher baru. Setiap operator membuat objek baru yang berlangganan ke Publisher hulu dan memancarkan nilai yang ditransformasi ke hilir. Karena Publisher adalah tipe generik, operator mempertahankan pengetikan ketat: map mentransformasi Output<A> menjadi Output<B>, tryMap menambahkan kemungkinan kesalahan. Combine berisi sekitar 100 operator bawaan.
| Kategori | Operator | Tujuan |
|---|---|---|
| Transformasi | map / tryMap / flatMap | Transformasi nilai atau aliran |
| Penyaringan | filter / compactMap / removeDuplicates | Seleksi atau pembersihan nilai |
| Kombinasi | combineLatest / zip / merge | Menggabungkan beberapa Publisher |
| Manajemen waktu | debounce / throttle / delay | Penundaan dan penjarangan peristiwa |
| Penanganan kesalahan | catch / retry / replaceError | Pemulihan setelah Failure |
| Manajemen Demand | buffer / collect | Pengelompokan atau buffering |
flatMap di Combine memiliki perbedaan penting dari versi RxSwift: ia menerima closure yang mengembalikan Publisher dengan tipe Failure yang sama dan membuka Publisher bersarang ke aliran utama. flatMap dengan maxPublishers: .max(1) berperilaku seperti switchMap — membatalkan Publisher bersarang sebelumnya saat nilai baru tiba. Ini sangat penting untuk skenario pencarian: saat memasukkan karakter baru, permintaan HTTP sebelumnya secara otomatis dibatalkan.
// Pencarian debounce dengan pembatalan permintaan sebelumnya
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)
Operator kombinasi — combineLatest dan zip — bekerja mirip dengan RxSwift: combineLatest memancarkan tuple dari nilai terakhir semua Publisher saat salah satu dari mereka berubah; zip menggabungkan nilai berpasangan berdasarkan indeks. merge menggabungkan Publisher dengan tipe yang sama menjadi satu aliran, preserveOrder tidak dijamin. Di Combine juga ada select — operator langka yang memilih Publisher pertama yang selesai dari beberapa, dan share — multicast aliran ke banyak pelanggan tanpa eksekusi ulang.
Scheduler di Combine — adalah protokol yang menentukan konteks eksekusi untuk operator. Tidak seperti RxSwift dengan 5+ Scheduler bawaan, Combine menggunakan mekanisme Apple yang ada: DispatchQueue, RunLoop dan OperationQueue. Masing-masing tipe ini sesuai dengan protokol Scheduler, memungkinkan mereka untuk diteruskan langsung ke receive(on:) dan subscribe(on:) tanpa adapter tambahan.
receive(on:) mengalihkan hilir ke Scheduler yang ditentukan — setara dengan observeOn di RxSwift. Semua operator setelah receive(on:) dijalankan pada Scheduler yang ditentukan. subscribe(on:) mengalihkan hulu — memengaruhi eksekusi Publisher. Pola umum: subscribe(on: DispatchQueue.global()) untuk pekerjaan latar belakang dan receive(on: DispatchQueue.main) untuk pembaruan UI. Di SwiftUI saat menggunakan .onReceive, pengikatan bawaan ke thread utama tidak diperlukan, tetapi untuk sink disarankan receive(on:).main secara eksplisit.
// Unduhan latar belakang + UI di thread utama
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 — alternatif untuk DispatchQueue.main untuk operasi UI. Perbedaannya adalah bahwa RunLoop.main terikat ke loop peristiwa aplikasi saat ini, sedangkan DispatchQueue.main terikat ke antrian global thread utama. Untuk UIKit disarankan DispatchQueue.main, untuk SwiftUI — RunLoop.main. ImmediateWhenScheduler menjalankan operasi secara sinkron di thread saat ini — digunakan secara default untuk pengujian dan Publisher sederhana.
ObservableObject — protokol SwiftUI untuk objek yang memublikasikan perubahan. Kelas yang mengimplementasikan ObservableObject dapat menggunakan property wrapper @Published untuk properti yang perubahannya secara otomatis memberi tahu SwiftUI tentang perlunya menggambar ulang. Di balik layar, @Published membuat Publisher yang memberi tahu Publisher objectWillChange saat wrappedValue berubah. SwiftUI berlangganan ke objectWillChange melalui @StateObject, @ObservedObject atau @EnvironmentObject.
@Published — cara paling umum untuk mengintegrasikan Combine di SwiftUI. Ketika nilai properti @Published berubah, SwiftUI memperbarui semua View yang menggunakan objek ini. @StateObject membuat instance ObservableObject dan berlangganan perubahannya. View yang dibuat dengan @StateObject secara otomatis menggambar ulang saat properti @Published berubah. Jika objek harus diteruskan antara beberapa View, @ObservedObject atau @EnvironmentObject digunakan.
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 — tipe-penghapus untuk Cancellable, yang menyimpan token pembatalan langganan. Set<AnyCancellable> mengelola siklus hidup langganan: saat pemilik dideinisialisasi, semua Cancellable secara otomatis dibatalkan. Di proyek SwiftUI, Set<AnyCancellable> dideklarasikan di kelas ObservableObject, dan langganan ditambahkan melalui .store(in: &cancellables). Untuk UIKit, mekanisme yang sama digunakan dengan penyimpanan di UIViewController melalui &cancellables atau panggilan manual cancel().
Combine dan RxSwift memecahkan tugas pemrograman reaktif yang sama, tetapi memiliki perbedaan arsitektur fundamental. Combine — bagian dari Apple SDK dengan kompatibilitas mundur hingga iOS 13, RxSwift — pustaka pihak ketiga dengan dukungan iOS 8+. Combine menggunakan pengetikan kesalahan ketat melalui Failure generic, RxSwift — tipe Error terpadu. Combine terintegrasi dengan SwiftUI di tingkat platform, RxSwift memerlukan RxCocoa untuk ekstensi UI.
Pilihan antara Combine dan RxSwift tergantung pada persyaratan proyek. Jika versi minimum iOS >= 13 dan proyek menggunakan SwiftUI — Combine adalah pilihan alami berkat integrasi bawaan dan tidak adanya dependensi tambahan. Jika proyek mendukung iOS 11-12, memiliki basis kode RxSwift yang ada atau memerlukan operator spesifik yang hanya tersedia di RxSwift (misalnya Observable.from(path:)), — RxSwift tetap menjadi solusi yang dibenarkan.
| Karakteristik | Combine | RxSwift |
|---|---|---|
| Pengembang | Apple (tertanam di SDK) | ReactiveX (komunitas) |
| Versi iOS | iOS 13+ | iOS 8+ |
| Tipe kesalahan | Generic Failure (Never untuk UI) | Error (apa pun) |
| Integrasi UI | @Published + SwiftUI | RxCocoa + UIKit |
| Operator | ~100 bawaan | 400+ operator |
| Swift Concurrency | Melalui .values (async sequence) | Melalui pustaka jembatan |
Pertanyaan yang sering diajukan
PassthroughSubject tidak menyimpan status — pelanggan hanya menerima peristiwa yang dikirim setelah berlangganan. CurrentValueSubject menyimpan nilai saat ini dan meneruskannya ke setiap pelanggan baru segera saat berlangganan. CurrentValueSubject cocok untuk mewakili status (misalnya isLoggedIn).
Langganan mengembalikan AnyCancellable, yang dibatalkan saat cancel() dipanggil atau saat deinisialisasi. Untuk manajemen grup gunakan Set<AnyCancellable> — semua langganan dibatalkan saat set dibersihkan. Ini adalah analog dari DisposeBag di RxSwift.
Ya, Combine tetap relevan untuk data streaming: peristiwa UI, debounce, combineLatest, WebSocket. async/await nyaman untuk permintaan tunggal, Combine — untuk aliran berkelanjutan atau ganda. Kedua framework saling melengkapi — Publisher dapat diubah menjadi AsyncSequence.
UIKit tidak memiliki Publisher bawaan, tetapi Apple menyediakan ekstensi: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. Untuk peristiwa UI kustom digunakan PassthroughSubject atau @IBAction yang dibungkus dalam Publisher melalui Future atau Subject.
Backpressure — mekanisme kontrol kecepatan aliran: Subscriber memberi tahu Publisher melalui Demand berapa banyak elemen yang siap diproses. Jika Demand = .max(1), Publisher menunggu permintaan sebelum mengirim nilai berikutnya. Ini mencegah luapan buffer saat kecepatan produsen dan konsumen tidak cocok.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga