Το Combine είναι ένα πλαίσιο αντιδραστικού προγραμματισμού από την Apple, που παρουσιάστηκε στο iOS 13. Σύμφωνα με το Apple Documentation, 2025, το Combine παρέχει ένα ενοποιημένο δηλωτικό API για την επεξεργασία ασύγχρονων γεγονότων. Ο Publisher καθορίζει την πηγή δεδομένων και ο Subscriber εγγράφεται για να τα λαμβάνει.
Κύρια
Combine είναι ένα δηλωτικό πλαίσιο Swift για την επεξεργασία ασύγχρονων γεγονότων στον χρόνο. Σε αντίθεση με προσεγγίσεις που βασίζονται σε callback, το Combine επιτρέπει την περιγραφή αγωγών επεξεργασίας δεδομένων μέσω μιας αλυσίδας τελεστών. Το πλαίσιο είναι ενσωματωμένο με το Foundation (URLSession, Timer, NotificationCenter) και το SwiftUI (ObservableObject, @Published).
Κάθε τύπος που υλοποιεί το πρωτόκολλο Publisher πρέπει να ορίζει Output (τύπος τιμών) και Failure (τύπος σφάλματος). Ο Publisher δεν είναι ενεργός έως την εγγραφή — αρχίζει να εκπέμπει γεγονότα μόνο μετά την κλήση του subscribe. Αυτή είναι ψυχρή σημασιολογία, χαρακτηριστική των αντιδραστικών ροών, που αυξάνει την αποδοτικότητα.
Subscriber λαμβάνει γεγονότα μέσω τριών μεθόδων: receive(subscription:) — επιβεβαίωση εγγραφής με διαχείριση demand; receive(_:) — λήψη νέας τιμής; receive(completion:) — ολοκλήρωση ροής με επιτυχία ή σφάλμα. Το Demand καθορίζει τον αριθμό τιμών που είναι έτοιμος να λάβει ο Subscriber — αυτός είναι ο μηχανισμός backpressure.
Η Apple παρέχει πολλούς ενσωματωμένους Publisher: Just εκπέμπει μία τιμή και ολοκληρώνεται, Future — ασύγχρονο αποτέλεσμα με σύλληψη, Deferred — καθυστερημένη δημιουργία Publisher έως τη στιγμή εγγραφής. Το URLSession.dataTaskPublisher μετατρέπει ένα αίτημα δικτύου σε αντιδραστική ροή. Το Timer.publish δημιουργεί έναν περιοδικό χρονοδιακόπτη. Το NotificationCenter.default.publisher μετατρέπει ειδοποιήσεις σε Publisher. Αυτές οι ενσωματώσεις επιτρέπουν την εγγραφή σε γεγονότα χωρίς μη αυτόματη γέφυρα.
Στο Combine, ο Publisher και ο Subscriber συνδέονται μέσω του πρωτοκόλλου Subscription. Το Subscription είναι ένα συνδετικό αντικείμενο που ελέγχει τη ροή δεδομένων. Ο προγραμματιστής δεν χρειάζεται να δημιουργεί δικούς του τύπους Publisher — η Apple παρέχει ενσωματωμένους: Just, Future, Deferred, Fail, Empty, καθώς και Publisher για URLSession και Timer.
Subject είναι ένας Publisher στον οποίο μπορούν να σταλούν μη αυτόματα τιμές. Ο PassthroughSubject δεν αποθηκεύει κατάσταση: ο συνδρομητής λαμβάνει μόνο γεγονότα που συνέβησαν μετά την εγγραφή. Ο CurrentValueSubject, παρόμοιος με τον BehaviorRelay στο RxSwift, αποθηκεύει την τρέχουσα τιμή και τη μεταδίδει αμέσως στον νέο συνδρομητή.
Ο μηχανισμός backpressure στο Combine ρυθμίζει την ταχύτητα μεταφοράς δεδομένων. Ο Subscriber ενημερώνει τον Publisher μέσω subscription.request(.unlimited) ή subscription.request(.max(N)) πόσες τιμές είναι έτοιμος να επεξεργαστεί. Αυτό αποτρέπει την υπερχείλιση προσωρινής μνήμης σε άνιση ταχύτητα παραγωγού και καταναλωτή.
Το Combine περιλαμβάνει περισσότερους από 100 τελεστές χωρισμένους σε κατηγορίες: μετασχηματισμός (map, tryMap, flatMap), φιλτράρισμα (filter, compactMap, removeDuplicates), συνδυασμός (combineLatest, merge, zip), διαχείριση χρόνου (debounce, throttle, delay) και διαχείριση σφαλμάτων (catch, replaceError, retry).
Ο τελεστής combineLatest συνδυάζει τις τελευταίες τιμές δύο Publisher — κάθε φορά που οποιοσδήποτε από αυτούς εκπέμπει μια νέα τιμή, η σύλληψη καλείται με τις δύο ενημερωμένες τιμές. Αυτός ο τελεστής είναι απαραίτητος στην επικύρωση φορμών όπου πρέπει να παρακολουθείται η κατάσταση πολλών πεδίων εισόδου ταυτόχρονα.
Ο τελεστής debounce καθυστερεί τη δημοσίευση τιμών έως ότου παρέλθει ένα καθορισμένο διάστημα χωρίς νέα γεγονότα. Αυτό είναι κρίσιμο για πεδία αναζήτησης: το αίτημα στον διακομιστή αποστέλλεται μόνο αφού ο χρήστης σταματήσει να πληκτρολογεί για 300-500 ms, μειώνοντας το φορτίο 5-10 φορές.
Το Combine προσφέρει διάφορες στρατηγικές διαχείρισης σφαλμάτων σε επίπεδο τελεστών. catch αναχαιτίζει το Failure και αντικαθιστά τον Publisher με έναν εφεδρικό — η ροή συνεχίζεται χωρίς κατάρρευση. replaceError(with:) τοποθετεί μια προεπιλεγμένη τιμή αντί του σφάλματος. retry(_:) επαναλαμβάνει την εγγραφή καθορισμένες φορές σε αποτυχία. Σημαντικό: ο τύπος σφάλματος Never στην υπογραφή του Publisher εγγυάται ότι η ροή δεν θα τελειώσει ποτέ με σφάλμα — αυτό επιτρέπει τη χρήση Publisher στο SwiftUI χωρίς διαχείριση Failure.
Το πρώτο παράδειγμα δείχνει βασική εργασία με το Just — Publisher που εκπέμπει μία τιμή και ολοκληρώνεται:
let publisher = Just("Hello Combine")
publisher
.sink(receiveCompletion: { completion in
print("Ολοκληρώθηκε: \(completion)")
}, receiveValue: { value in
print("Λήφθηκε: \(value)")
})
Το δεύτερο παράδειγμα δείχνει την ενσωμάτωση του Combine με URLSession για αίτημα δικτύου με επεξεργασία αποτελέσματος στο κύριο νήμα:
let url = URL(string: "https://api.example.com")!
URLSession.shared
.dataTaskPublisher(for: url)
.map { $0.data }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
if case .failure(let error) = completion {
print("Σφάλμα: \(error)")
}
}, receiveValue: { data in
print("Δεδομένα λήφθηκαν: \(data.count)")
})
Το τρίτο παράδειγμα — χρήση CurrentValueSubject για διαχείριση κατάστασης:
let counter = CurrentValueSubject<Int, Never>(0)
counter
.sink { value in
print("Μετρητής: \(value)")
}
counter.send(1) // Θα εμφανίσει: Μετρητής: 1
counter.send(2) // Θα εμφανίσει: Μετρητής: 2
print(counter.value) // Θα εμφανίσει: 2
Combine και RxSwift λύνουν την ίδια εργασία — αντιδραστικό προγραμματισμό, αλλά με διαφορετικές προσεγγίσεις. Το Combine είναι ένα εγγενές πλαίσιο Apple ενσωματωμένο στο σύστημα: δεν απαιτεί εγκατάσταση εξαρτήσεων, είναι ενσωματωμένο με SwiftUI και Foundation. Το RxSwift είναι βιβλιοθήκη τρίτου μέρους με υποστήριξη iOS 9+ και ευρύτερο σύνολο τελεστών.
Η απόδοση του Combine είναι υψηλότερη σε τυπικά σενάρια χάρη στην εγγενή υλοποίηση σε C. Το RxSwift προσφέρει περισσότερα εργαλεία εντοπισμού σφαλμάτων (RxSwift.Resources.total, Debug), αλλά η αντικειμενοστραφής αρχιτεκτονική του μπορεί να είναι λιγότερο αποδοτική σε μεγάλες ροές δεδομένων. Η επιλογή μεταξύ τους είναι ένας συμβιβασμός μεταξύ δυνατοτήτων και ενσωμάτωσης.
Μια σημαντική διαφορά — το μοντέλο backpressure. Στο Combine, ο Subscriber διαχειρίζεται το demand μέσω Subscription, που δίνει σαφή έλεγχο της ταχύτητας ροής. Στο RxSwift, το backpressure δεν ελέγχεται προεπιλεγμένα — το Observable εκπέμπει όλες τις τιμές και ο Subscriber τις επεξεργάζεται καθώς φτάνουν. Για μεγάλες ροές δεδομένων, το Combine αποδεικνύεται πιο προβλέψιμο και ασφαλέστερο στη μνήμη.
Το Combine είναι βαθιά ενσωματωμένο με το SwiftUI σε επίπεδο γλώσσας. Το @Published δημιουργεί Publisher, το ObservableObject παρέχει objectWillChange Publisher. Το SwiftUI View εγγράφεται αυτόματα σε αυτούς τους Publisher και ανασχεδιάζεται σε αλλαγές. Για το UIKit υπάρχουν εξωτερικά περιτυλίγματα όπως το CombineCocoa, αλλά το Combine σχεδιάστηκε αρχικά για SwiftUI, επομένως η ενσωμάτωσή του στο UIKit απαιτεί πρόσθετο κώδικα. Το RxSwift, αντίθετα, έχει πλούσιο οικοσύστημα για UIKit: το RxCocoa παρέχει αντιδραστικά περιτυλίγματα για όλα τα στοιχεία UI.
Το Combine υποστηρίζει ενσωμάτωση με Core Data μέσω NSFetchedResultsController Publisher. Ο Publisher εκπέμπει νέα δεδομένα σε κάθε αλλαγή της στοίβας Core Data: προσθήκη, διαγραφή, ενημέρωση εγγραφών. Αυτό επιτρέπει τη δημιουργία αντιδραστικών λιστών χωρίς μη αυτόματη κλήση reloadData. Συνδυάζοντας το Combine με NotificationCenter Publisher για UIApplication.willResignActiveNotification, ο προγραμματιστής δημιουργεί ένα αντιδραστικό επίπεδο για ολόκληρο τον κύκλο ζωής της εφαρμογής χωρίς εκπροσώπους και callback. Για κλάσεις ObservableObject, το SwiftUI ενημερώνει αυτόματα το View κατά την αλλαγή ιδιοτήτων @Published — το Combine παρέχει ολόκληρη τη σύνδεση χωρίς μία γραμμή μη αυτόματου κώδικα. Ο αρθρωτός έλεγχος κώδικα Combine εκτελείται μέσω XCTestExpectation και Publisher.sink σε περιπτώσεις δοκιμής με απομόνωση Scheduler μέσω ImmediateScheduler.
Κατά την εργασία με Combine σε έργα UIKit, οι προγραμματιστές συχνά χρησιμοποιούν εγγραφή μέσω sink και αποθηκεύουν AnyCancellable σε Set. Το τυπικό μοτίβο: το ViewController δημιουργεί μια προβολή, αποθηκεύει cancellables, εγγράφεται σε Publisher από το ViewModel. Στο deinit, όλες οι εγγραφές ακυρώνονται αυτόματα μέσω της συλλογής Cancellables. Για σωστή εργασία με UIControl στο Combine, μπορεί να χρησιμοποιηθεί UIControl.Event Publisher μέσω της επέκτασης publisher(for:).
Ο εντοπισμός σφαλμάτων Combine γίνεται μέσω του τελεστή print() ή handleEvents(). Το print(String) καταγράφει όλα τα γεγονότα: receive subscription, request demand, receive value, receive completion. Το handleEvents δίνει λεπτότερο έλεγχο: μπορούν να καθοριστούν συλλήψεις για κάθε στάδιο του κύκλου ζωής του Publisher. Για δοκιμή Combine υπάρχει το CombineExpectations — μια βιβλιοθήκη που επιτρέπει τον έλεγχο ότι ο Publisher εκπέμπει τις αναμενόμενες τιμές σε δοκιμές με εικονικό χρόνο και έλεγχο demand.
Το Combine χρησιμοποιείται ενεργά στο SwiftUI για διαχείριση φορμών: κάθε ιδιότητα @Published δημιουργεί Publisher, το SwiftUI εγγράφεται σε αυτό μέσω View.body. Συνδυάζοντας πολλούς Publisher μέσω combineLatest, η επικύρωση φόρμας γίνεται δηλωτική: κάθε Publisher παρακολουθεί ένα πεδίο, το combineLatest συλλέγει όλες τις τιμές, το map τις ελέγχει και επιστρέφει την κατάσταση του κουμπιού Submit. Το Publishers.Merge συνδυάζει πολλούς Publisher ίδιου τύπου σε έναν — χρήσιμο για συλλογή γεγονότων από διαφορετικά χειριστήρια σε μία ροή.
Για ενσωμάτωση Combine με UIKit χρησιμοποιήστε UIControl Publisher μέσω επέκτασης: το button.publisher(for: .touchUpInside) επιστρέφει Publisher που εκπέμπει γεγονός κατά το πάτημα. CombineCocoa — μια βιβλιοθήκη κοινότητας που παρέχει Publisher για όλα τα γεγονότα UIControl, UITextView.text, UIScrollView.contentOffset. Κατά την ακύρωση εγγραφής, το cancellables Store καθαρίζεται στο deinit, διασφαλίζοντας ότι καμία εγγραφή δεν επιβιώνει του ιδιοκτήτη της. Για UIControl με πολλαπλές καταστάσεις χρησιμοποιήστε Publishers.MergeMany για συνδυασμό Publisher σε πίνακα.
Συχνές Ερωτήσεις
Combine είναι διαθέσιμο από iOS 13, macOS 10.15, tvOS 13 και watchOS 6. Αυτό περιορίζει τη χρήση του σε έργα που απαιτούν υποστήριξη παλαιότερων εκδόσεων. Για iOS 12 και κάτω εφαρμόζεται RxSwift ή βιβλιοθήκες τρίτων.
PassthroughSubject δεν αποθηκεύει κατάσταση και δεν επαναλαμβάνει την τελευταία τιμή σε νέους συνδρομητές — λαμβάνουν μόνο μελλοντικά γεγονότα. Το CurrentValueSubject αποθηκεύει την τρέχουσα τιμή και τη μεταδίδει αμέσως στον νέο συνδρομητή. Το CurrentValueSubject.value επιτρέπει την ανάγνωση και εγγραφή της τρέχουσας τιμής σύγχρονα.
Combine βρίσκεται στη βάση του SwiftUI: το @Published δημιουργεί Publisher, το ObservableObject χρησιμοποιεί το objectWillChange Publisher. Το SwiftUI View εγγράφεται αυτόματα σε @ObservedObject και @StateObject μέσω του αγωγού Combine, ανασχεδιάζοντας το View κατά την αλλαγή ιδιοτήτων.
Backpressure — μηχανισμός διαχείρισης ταχύτητας μεταφοράς δεδομένων. Ο Subscriber ενημερώνει το Subscription πόσες τιμές είναι έτοιμος να επεξεργαστεί (demand). Ο Publisher δεν μπορεί να εκπέμψει περισσότερα από όσα ζητήθηκαν. Αυτό είναι κρίσιμο κατά την εργασία με μεγάλες ροές δεδομένων από υποδοχές δικτύου.
Ο τελεστής catch αναχαιτίζει το σφάλμα και αντικαθιστά τον Publisher με fallback. Το replaceError αντικαθιστά το σφάλμα με προεπιλεγμένη τιμή. Το retry επαναλαμβάνει την εγγραφή καθορισμένες φορές σε σφάλμα. Όλοι οι τελεστές πρέπει να τοποθετούνται πριν από το subscribe για να αποκλειστεί κατάρρευση σε Failure.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης