Combine — ay isang native na reaktibong programming framework mula sa Apple, na ipinakilala sa iOS 13, macOS Catalina, tvOS 13 at watchOS 6. Nagbibigay ito ng deklaratibong Swift API para sa pagproseso ng mga asynchronous na kaganapan sa pamamagitan ng pattern ng Publisher at Subscriber, na pinapalitan ang mga delegate, closure at NotificationCenter ng isang pinag-isang chain. Ayon sa Apple, 2025, ang Combine ay pundasyon para sa SwiftUI at modernong iOS architecture, na gumagana nang malapit sa async/await at Structured Concurrency. Ang framework ay dinisenyo para sa komposisyon ng mga asynchronous na operasyon na may garantiya ng thread safety.
Mga pangunahing punto
Combine — ay isang deklaratibong reaktibong programming framework na nakapaloob sa Apple SDK. Ipinapatupad nito ang pattern ng Reactive Streams: Publisher ay gumagawa ng mga halaga, Subscriber ay kumokonsumo ng mga ito, at mga operator ay nagbabago ng stream sa pagitan nila. Nalulutas ng Combine ang problema ng mga callback at delegate sa pamamagitan ng pagbibigay ng pinag-isang modelo ng komposisyon para sa anumang asynchronous na kaganapan — mula sa mga tugon sa network hanggang sa mga pagbabago sa UI.
Bago ang pagdating ng Combine, ang mga iOS developer ay gumagamit ng mga third-party na library, pangunahin ang RxSwift. Nilikha ng Apple ang Combine bilang isang native na alternatibo na may malalim na integrasyon sa ecosystem: ang framework ay sumusuporta sa Objective-C sa pamamagitan ng @objc bridges, gumagana sa KVO (Key-Value Observing sa pamamagitan ng NSObject.keyValuePublisher) at NotificationCenter, at ito rin ang batayan para sa SwiftUI. Lahat ng UIKit component na nai-publish sa SwiftUI ay gumagamit ng Combine sa ilalim ng hood para sa pag-update ng mga view.
Ang Combine ay dinisenyo na isinasaalang-alang ang Swift Concurrency: mula sa iOS 15, ang Publisher ay maaaring gawing AsyncSequence sa pamamagitan ng .values at gamitin sa for-await-in loops. Ang reverse conversion ng async function sa Publisher ay ginagawa sa pamamagitan ng Future. Ayon sa Apple WWDC 2024, ang Combine ay nananatiling inirerekomendang framework para sa pagproseso ng streaming data sa UIKit application, sa kabila ng pagdating ng async/await para sa mga solong asynchronous na tawag.
Ang Combine ay nakabatay sa tatlong protocol: Publisher (naglalabas ng mga halaga ng uri ng Output, maaaring magtapos sa error ng uri ng Failure), Subscriber (tumatanggap ng mga halaga, namamahala ng Demand — bilang ng mga hinihiling na elemento), Subscription (kumakatawan sa koneksyon ng Publisher-Subscriber na may kakayahang kanselahin). Ang channel ng paghahatid ng data ay sinisimulan sa subscribe at nagtatapos sa cancel, completion o error. Demand — natatanging konsepto ng Combine: Ipinapaalam ng Subscriber sa Publisher kung gaano karaming elemento ang handa nitong iproseso, na nagpapatupad ng backpressure sa antas ng protocol.
Publisher — protocol na may dalawang kaugnay na uri: Output (uri ng mga inilalabas na halaga) at Failure (uri ng error na nagpapatupad ng Error). Kung ang stream ay hindi maaaring magtapos sa error, ang Failure ay tinukoy bilang Never — ginagarantiyahan nito sa Subscriber na ang onReceive ay tatawagin lamang na may Output. Kasama sa mga built-in na Publisher ang Just (iisang halaga), Sequence (array), URLSession.DataTaskPublisher (network request), NotificationCenter.Publisher at @Published property wrapper.
import Combine
// Paglikha ng Publisher mula sa sequence
let publisher = [1, 2, 3, 4, 5].publisher
// Paglikha ng Subscriber na may pagproseso ng halaga
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("Nakumpleto")
}
}
publisher.subscribe(PrintSubscriber())
Subscription — ay isang protocol na kumakatawan sa aktibong koneksyon sa pagitan ng Publisher at Subscriber. Natatanggap ng Subscriber ang Subscription sa pamamaraang receive(subscription:) at tinatawag ang request(_:) para tukuyin ang Demand: .unlimited (lahat ng halaga), .max(N) (limitadong bilang) o .none (pause). Ang Demand ay maaaring dynamic na magbago — maaaring dagdagan o bawasan ng Subscriber ang bilang ng mga hinihiling na elemento sa proseso ng pagtanggap ng data. Tinitiyak nito ang backpressure nang walang buffering sa panig ng Publisher.
Subject — ay isang uri na pinagsasama ang Publisher at Subscriber. Ang Subject ay maaaring gamitin bilang Publisher (maaaring mag-subscribe dito) at sabay bilang Subscriber (maaaring magpadala ng mga halaga dito). Ang Combine ay nagbibigay ng dalawang uri ng Subject: PassthroughSubject (hindi nag-iimbak ng estado, nagpapasa lamang ng mga bagong halaga) at CurrentValueSubject (nag-iimbak ng kasalukuyang halaga at ipinapasa ito sa mga bagong subscriber). Ang Subject ay kinakailangan para sa integrasyon ng imperative code sa mga reaktibong chain ng Combine.
let subject = PassthroughSubject<String, Never>()
// Subscription bilang Publisher
let cancellable = subject
.map { $0.uppercased() }
.sink { print($0) }
// Pagpapadala ng mga halaga bilang Subscriber
subject.send("hello") // Nag-print ng “HELLO”
subject.send("mundo") // Nag-print ng “WORLD”
CurrentValueSubject ay naiiba sa PassthroughSubject sa pagkakaroon ng paunang halaga at property na value: ang subscriber ay agad na tumatanggap ng kasalukuyang halaga sa pag-subscribe, pagkatapos lahat ng kasunod na update. Ang CurrentValueSubject.value ay accessible para sa pagbasa at pagsulat — ang pagbabago ng value ay awtomatikong nagpapadala ng bagong halaga sa lahat ng subscriber. Ginagawa nitong perpektong pagpili ang CurrentValueSubject para sa representasyon ng estado sa MVVM architecture: Ang ViewModel ay nag-publish ng CurrentValueSubject, ang View ay nag-subscribe na may mga pagbabago sa pamamagitan ng sink.
Parehong Subject ay nagpapahintulot sa pagtatapos ng stream sa pamamagitan ng pagtawag ng send(completion: .finished) o send(completion: .failure(error)). Pagkatapos ng pagtatapos, ang Subject ay humihinto sa pagtanggap at pagpapasa ng mga kaganapan. Para sa mga pangmatagalang stream na hindi dapat magtapos (hal. UI events), inirerekomenda ang paggamit ng PassthroughSubject na may Never Failure upang ibukod ang hindi sinasadyang pagtawag ng send(completion:).
Mga Operator Combine — ay mga pamamaraan ng Publisher na nagbabalik ng bagong Publisher. Bawat operator ay lumilikha ng bagong object na nag-subscribe sa upstream Publisher at naglalabas ng mga binagong halaga downstream. Dahil ang Publisher ay isang generic na uri, pinapanatili ng mga operator ang mahigpit na pag-type: ang map ay nagbabago ng Output<A> sa Output<B>, ang tryMap ay nagdaragdag ng posibilidad ng error. Ang Combine ay naglalaman ng humigit-kumulang 100 built-in na operator.
| Kategorya | Operator | Layunin |
|---|---|---|
| Pagbabago | map / tryMap / flatMap | Pagbabago ng mga halaga o stream |
| Pagsala | filter / compactMap / removeDuplicates | Pagpili o paglilinis ng mga halaga |
| Pagsasama | combineLatest / zip / merge | Pagsasama ng maraming Publisher |
| Pamamahala ng oras | debounce / throttle / delay | Pagkaantala at pagpapaliit ng mga kaganapan |
| Pamamahala ng error | catch / retry / replaceError | Pagbawi pagkatapos ng Failure |
| Pamamahala ng Demand | buffer / collect | Pag-grupo o buffering |
flatMap sa Combine ay may mahalagang pagkakaiba sa bersyon ng RxSwift: tumatanggap ito ng closure na nagbabalik ng Publisher na may parehong uri ng Failure at binubuksan ang nested Publisher sa pangunahing stream. Ang flatMap na may maxPublishers: .max(1) ay kumikilos tulad ng switchMap — kinakansela ang dating nested Publisher sa pagdating ng bagong halaga. Ito ay kritikal para sa mga senaryo ng paghahanap: sa pagpasok ng bagong character, ang dating HTTP request ay awtomatikong kinakansela.
// Debounce na paghahanap na may pagkansela ng nakaraang request
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)
Mga operator ng pagsasama — combineLatest at zip — gumagana tulad ng sa RxSwift: ang combineLatest ay naglalabas ng tuple ng huling halaga ng lahat ng Publisher kapag nagbago ang alinman sa kanila; ang zip ay pinagsasama ang mga halaga nang pares ayon sa index. Ang merge ay pinagsasama ang mga Publisher ng parehong uri sa isang stream, ang preserveOrder ay hindi garantisado. Sa Combine mayroon ding select — isang bihirang operator na pumipili ng unang natapos na Publisher mula sa marami, at share — multicast ng stream sa maraming subscriber nang walang paulit-ulit na pagpapatupad.
Scheduler sa Combine — ay isang protocol na tumutukoy sa konteksto ng pagpapatupad para sa mga operator. Hindi tulad ng RxSwift na may 5+ built-in na Scheduler, ang Combine ay gumagamit ng umiiral na mekanismo ng Apple: DispatchQueue, RunLoop at OperationQueue. Bawat isa sa mga uri na ito ay tumutugma sa Scheduler protocol, na nagpapahintulot sa direktang pagpasa sa receive(on:) at subscribe(on:) nang walang karagdagang adapter.
receive(on:) ay nagpapalipat ng downstream sa tinukoy na Scheduler — katumbas ng observeOn sa RxSwift. Lahat ng operator pagkatapos ng receive(on:) ay isinasagawa sa tinukoy na Scheduler. subscribe(on:) ay nagpapalipat ng upstream — nakakaapekto sa pagpapatupad ng Publisher. Karaniwang pattern: subscribe(on: DispatchQueue.global()) para sa background work at receive(on: DispatchQueue.main) para sa UI update. Sa SwiftUI kapag gumagamit ng .onReceive, hindi kinakailangan ang built-in na binding sa main thread, ngunit para sa sink inirerekomenda ang explicit na receive(on:).main.
// Background download + UI sa pangunahing thread
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 — alternatibo sa DispatchQueue.main para sa UI operations. Ang pagkakaiba ay ang RunLoop.main ay nakatali sa kasalukuyang event loop ng application, samantalang ang DispatchQueue.main ay nakatali sa global queue ng main thread. Para sa UIKit inirerekomenda ang DispatchQueue.main, para sa SwiftUI — RunLoop.main. ImmediateWhenScheduler ay nagsasagawa ng mga operasyon nang synchronously sa kasalukuyang thread — ginagamit bilang default para sa mga pagsubok at simpleng Publisher.
ObservableObject — SwiftUI protocol para sa mga object na nag-publish ng mga pagbabago. Ang klase na nagpapatupad ng ObservableObject ay maaaring gumamit ng property wrapper @Published para sa mga property na ang mga pagbabago ay awtomatikong nagpapaalam sa SwiftUI tungkol sa pangangailangan ng pag-redraw. Sa ilalim ng hood, ang @Published ay lumilikha ng Publisher na nagpapaalam sa Publisher objectWillChange kapag nagbago ang wrappedValue. Ang SwiftUI ay nag-subscribe sa objectWillChange sa pamamagitan ng @StateObject, @ObservedObject o @EnvironmentObject.
@Published — ang pinakakaraniwang paraan ng pagsasama ng Combine sa SwiftUI. Kapag nagbago ang halaga ng @Published property, ina-update ng SwiftUI ang lahat ng View na gumagamit ng object na ito. @StateObject ay lumilikha ng instance ng ObservableObject at nag-subscribe sa mga pagbabago nito. Ang View na nilikha gamit ang @StateObject ay awtomatikong nagre-redraw kapag nagbago ang @Published properties. Kung ang object ay kailangang ipasa sa maraming View, ginagamit ang @ObservedObject o @EnvironmentObject.
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 — type-eraser para sa Cancellable, na nag-iimbak ng cancellation token ng subscription. Ang Set<AnyCancellable> ay namamahala sa lifecycle ng mga subscription: sa deinitialization ng may-ari, lahat ng Cancellable ay awtomatikong kinakansela. Sa SwiftUI projects, ang Set<AnyCancellable> ay idineklara sa ObservableObject class, at ang mga subscription ay idinaragdag sa pamamagitan ng .store(in: &cancellables). Para sa UIKit, ginagamit ang parehong mekanismo na may imbakan sa UIViewController sa pamamagitan ng &cancellables o manual na pagtawag ng cancel().
Combine at RxSwift ay lumulutas ng parehong gawain ng reaktibong programming, ngunit may mga pangunahing pagkakaiba sa arkitektura. Combine — bahagi ng Apple SDK na may backward compatibility hanggang iOS 13, RxSwift — third-party library na may suporta sa iOS 8+. Combine ay gumagamit ng mahigpit na pag-type ng error sa pamamagitan ng Failure generic, RxSwift — ang nag-iisang uri ng Error. Combine ay isinama sa SwiftUI sa antas ng platform, RxSwift ay nangangailangan ng RxCocoa para sa UI extensions.
Ang pagpili sa pagitan ng Combine at RxSwift ay nakadepende sa mga kinakailangan ng proyekto. Kung ang minimum na bersyon ng iOS >= 13 at ang proyekto ay gumagamit ng SwiftUI — ang Combine ay natural na pagpili dahil sa built-in na integrasyon at kawalan ng karagdagang dependencies. Kung ang proyekto ay sumusuporta sa iOS 11-12, naglalaman ng umiiral na RxSwift codebase o nangangailangan ng mga tiyak na operator na available lamang sa RxSwift (hal. Observable.from(path:)), — ang RxSwift ay nananatiling makatuwirang solusyon.
| Katangian | Combine | RxSwift |
|---|---|---|
| Nag-develop | Apple (nakapaloob sa SDK) | ReactiveX (komunidad) |
| Bersyon ng iOS | iOS 13+ | iOS 8+ |
| Uri ng error | Generic Failure (Never para sa UI) | Error (anumang) |
| Integrasyon ng UI | @Published + SwiftUI | RxCocoa + UIKit |
| Mga Operator | ~100 built-in | 400+ operator |
| Swift Concurrency | Sa pamamagitan ng .values (async sequence) | Sa pamamagitan ng bridge library |
Mga madalas itanong
PassthroughSubject ay hindi nag-iimbak ng estado — ang subscriber ay tumatanggap lamang ng mga kaganapang ipinadala pagkatapos ng subscription. CurrentValueSubject ay nag-iimbak ng kasalukuyang halaga at ipinapasa ito sa bawat bagong subscriber kaagad sa pag-subscribe. Ang CurrentValueSubject ay angkop para sa representasyon ng estado (hal. isLoggedIn).
Ang subscription ay nagbabalik ng AnyCancellable, na kinakansela kapag tinawag ang cancel() o sa deinitialization. Para sa group management gamitin ang Set<AnyCancellable> — lahat ng subscription ay kinakansela kapag na-clear ang set. Ito ay katumbas ng DisposeBag mula sa RxSwift.
Oo, ang Combine ay nananatiling may kaugnayan para sa streaming data: UI events, debounce, combineLatest, WebSocket. Ang async/await ay maginhawa para sa mga solong request, Combine — para sa tuluy-tuloy o maramihang stream. Ang parehong framework ay komplementaryo — ang Publisher ay maaaring gawing AsyncSequence.
Ang UIKit ay walang built-in na Publisher, ngunit ang Apple ay nagbibigay ng extensions: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher. Para sa custom na UI events ginagamit ang PassthroughSubject o @IBAction na nakabalot sa Publisher sa pamamagitan ng Future o Subject.
Backpressure — mekanismo ng pagkontrol ng bilis ng stream: Ipinapaalam ng Subscriber sa Publisher sa pamamagitan ng Demand kung gaano karaming elemento ang handa nitong iproseso. Kung ang Demand = .max(1), ang Publisher ay naghihintay ng request bago magpadala ng susunod na halaga. Pinipigilan nito ang buffer overflow kapag hindi tugma ang bilis ng producer at consumer.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din