Combine: τι είναι, βασικές έννοιες και Publisher με Subscriber

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-03-17 Χρόνος ανάγνωσης: 8 λεπ

Το Combine είναι ένα πλαίσιο αντιδραστικού προγραμματισμού από την Apple, που παρουσιάστηκε στο iOS 13. Σύμφωνα με το Apple Documentation, 2025, το Combine παρέχει ένα ενοποιημένο δηλωτικό API για την επεξεργασία ασύγχρονων γεγονότων. Ο Publisher καθορίζει την πηγή δεδομένων και ο Subscriber εγγράφεται για να τα λαμβάνει.

Κύρια

  • Combine — ενσωματωμένο πλαίσιο Apple για αντιδραστικό προγραμματισμό, διαθέσιμο από iOS 13
  • Publisher — πρωτόκολλο που καθορίζει την πηγή τιμών Output με δυνατότητα σφάλματος Failure
  • Subscriber — πρωτόκολλο που λαμβάνει τιμές από τον Publisher με διαχειριζόμενο demand
  • Subject — Publisher που επιτρέπει μη αυτόματη εισαγωγή γεγονότων (PassthroughSubject, CurrentValueSubject)
  • Operators — map, filter, combineLatest, flatMap για μετασχηματισμό και συνδυασμό ροών

Τι είναι το Combine;

Combine είναι ένα δηλωτικό πλαίσιο Swift για την επεξεργασία ασύγχρονων γεγονότων στον χρόνο. Σε αντίθεση με προσεγγίσεις που βασίζονται σε callback, το Combine επιτρέπει την περιγραφή αγωγών επεξεργασίας δεδομένων μέσω μιας αλυσίδας τελεστών. Το πλαίσιο είναι ενσωματωμένο με το Foundation (URLSession, Timer, NotificationCenter) και το SwiftUI (ObservableObject, @Published).

Πρωτόκολλο Publisher

Κάθε τύπος που υλοποιεί το πρωτόκολλο Publisher πρέπει να ορίζει Output (τύπος τιμών) και Failure (τύπος σφάλματος). Ο Publisher δεν είναι ενεργός έως την εγγραφή — αρχίζει να εκπέμπει γεγονότα μόνο μετά την κλήση του subscribe. Αυτή είναι ψυχρή σημασιολογία, χαρακτηριστική των αντιδραστικών ροών, που αυξάνει την αποδοτικότητα.

Πρωτόκολλο Subscriber

Subscriber λαμβάνει γεγονότα μέσω τριών μεθόδων: receive(subscription:) — επιβεβαίωση εγγραφής με διαχείριση demand; receive(_:) — λήψη νέας τιμής; receive(completion:) — ολοκλήρωση ροής με επιτυχία ή σφάλμα. Το Demand καθορίζει τον αριθμό τιμών που είναι έτοιμος να λάβει ο Subscriber — αυτός είναι ο μηχανισμός backpressure.

Ενσωματωμένοι Publisher

Η Apple παρέχει πολλούς ενσωματωμένους Publisher: Just εκπέμπει μία τιμή και ολοκληρώνεται, Future — ασύγχρονο αποτέλεσμα με σύλληψη, Deferred — καθυστερημένη δημιουργία Publisher έως τη στιγμή εγγραφής. Το URLSession.dataTaskPublisher μετατρέπει ένα αίτημα δικτύου σε αντιδραστική ροή. Το Timer.publish δημιουργεί έναν περιοδικό χρονοδιακόπτη. Το NotificationCenter.default.publisher μετατρέπει ειδοποιήσεις σε Publisher. Αυτές οι ενσωματώσεις επιτρέπουν την εγγραφή σε γεγονότα χωρίς μη αυτόματη γέφυρα.

Publisher και Subscriber στο Combine

Στο Combine, ο Publisher και ο Subscriber συνδέονται μέσω του πρωτοκόλλου Subscription. Το Subscription είναι ένα συνδετικό αντικείμενο που ελέγχει τη ροή δεδομένων. Ο προγραμματιστής δεν χρειάζεται να δημιουργεί δικούς του τύπους Publisher — η Apple παρέχει ενσωματωμένους: Just, Future, Deferred, Fail, Empty, καθώς και Publisher για URLSession και Timer.

Subject στο Combine

Subject είναι ένας Publisher στον οποίο μπορούν να σταλούν μη αυτόματα τιμές. Ο PassthroughSubject δεν αποθηκεύει κατάσταση: ο συνδρομητής λαμβάνει μόνο γεγονότα που συνέβησαν μετά την εγγραφή. Ο CurrentValueSubject, παρόμοιος με τον BehaviorRelay στο RxSwift, αποθηκεύει την τρέχουσα τιμή και τη μεταδίδει αμέσως στον νέο συνδρομητή.

Backpressure

Ο μηχανισμός backpressure στο Combine ρυθμίζει την ταχύτητα μεταφοράς δεδομένων. Ο Subscriber ενημερώνει τον Publisher μέσω subscription.request(.unlimited) ή subscription.request(.max(N)) πόσες τιμές είναι έτοιμος να επεξεργαστεί. Αυτό αποτρέπει την υπερχείλιση προσωρινής μνήμης σε άνιση ταχύτητα παραγωγού και καταναλωτή.

Τελεστές Combine

Το 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.

Παραδείγματα κώδικα Combine

Το πρώτο παράδειγμα δείχνει βασική εργασία με το Just — Publisher που εκπέμπει μία τιμή και ολοκληρώνεται:

swift
let publisher = Just("Hello Combine")

publisher
    .sink(receiveCompletion: { completion in
        print("Ολοκληρώθηκε: \(completion)")
    }, receiveValue: { value in
        print("Λήφθηκε: \(value)")
    })

Το δεύτερο παράδειγμα δείχνει την ενσωμάτωση του Combine με URLSession για αίτημα δικτύου με επεξεργασία αποτελέσματος στο κύριο νήμα:

swift
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 για διαχείριση κατάστασης:

swift
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 vs RxSwift

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 στην πράξη

Το 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 σε πίνακα.

Συχνές Ερωτήσεις

Από ποια έκδοση iOS είναι διαθέσιμο το Combine;

Combine είναι διαθέσιμο από iOS 13, macOS 10.15, tvOS 13 και watchOS 6. Αυτό περιορίζει τη χρήση του σε έργα που απαιτούν υποστήριξη παλαιότερων εκδόσεων. Για iOS 12 και κάτω εφαρμόζεται RxSwift ή βιβλιοθήκες τρίτων.

Σε τι διαφέρει το PassthroughSubject από το CurrentValueSubject;

PassthroughSubject δεν αποθηκεύει κατάσταση και δεν επαναλαμβάνει την τελευταία τιμή σε νέους συνδρομητές — λαμβάνουν μόνο μελλοντικά γεγονότα. Το CurrentValueSubject αποθηκεύει την τρέχουσα τιμή και τη μεταδίδει αμέσως στον νέο συνδρομητή. Το CurrentValueSubject.value επιτρέπει την ανάγνωση και εγγραφή της τρέχουσας τιμής σύγχρονα.

Πώς ενσωματώνεται το Combine με το SwiftUI;

Combine βρίσκεται στη βάση του SwiftUI: το @Published δημιουργεί Publisher, το ObservableObject χρησιμοποιεί το objectWillChange Publisher. Το SwiftUI View εγγράφεται αυτόματα σε @ObservedObject και @StateObject μέσω του αγωγού Combine, ανασχεδιάζοντας το View κατά την αλλαγή ιδιοτήτων.

Τι είναι το backpressure στο Combine;

Backpressure — μηχανισμός διαχείρισης ταχύτητας μεταφοράς δεδομένων. Ο Subscriber ενημερώνει το Subscription πόσες τιμές είναι έτοιμος να επεξεργαστεί (demand). Ο Publisher δεν μπορεί να εκπέμψει περισσότερα από όσα ζητήθηκαν. Αυτό είναι κρίσιμο κατά την εργασία με μεγάλες ροές δεδομένων από υποδοχές δικτύου.

Πώς γίνεται η διαχείριση σφαλμάτων στο Combine;

Ο τελεστής catch αναχαιτίζει το σφάλμα και αντικαθιστά τον Publisher με fallback. Το replaceError αντικαθιστά το σφάλμα με προεπιλεγμένη τιμή. Το retry επαναλαμβάνει την εγγραφή καθορισμένες φορές σε σφάλμα. Όλοι οι τελεστές πρέπει να τοποθετούνται πριν από το subscribe για να αποκλειστεί κατάρρευση σε Failure.

Σύνοψη

  • Combine — ενσωματωμένο πλαίσιο Apple για αντιδραστικό προγραμματισμό από iOS 13
  • Publisher — πρωτόκολλο με Output και Failure, ξεκινά μετά από εγγραφή
  • Subscriber — λαμβάνει τιμές μέσω receive με υποστήριξη demand
  • Subject (PassthroughSubject, CurrentValueSubject) — Publisher με μη αυτόματη εισαγωγή γεγονότων
  • Τελεστές map, filter, combineLatest, debounce, catch — βάση του αγωγού
  • Backpressure μέσω demand αποτρέπει υπερχείλιση προσωρινής μνήμης Subscriber
  • Επιλέξτε Combine για νέα έργα από iOS 13, RxSwift — για υποστήριξη παλαιών εκδόσεων

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης