Callback — τι είναι, συναρτήσεις επιστροφής κλήσης και πώς λειτουργούν

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

Callback — είναι μια συνάρτηση που μεταβιβάζεται σε μια άλλη συνάρτηση ως όρισμα και εκτελείται μετά την ολοκλήρωση μιας ασύγχρονης λειτουργίας. Στην ανάπτυξη εφαρμογών για κινητά, το callback χρησιμοποιείται για την επεξεργασία αποτελεσμάτων αιτημάτων δικτύου, εργασία με βάσεις δεδομένων και κινούμενα σχέδια. Σύμφωνα με το Apple Documentation (2025), τα closures στη Swift είναι η βασική μορφή callback και χρησιμοποιούνται σε URLSession, GCD και Combine. Στο Android, το callback υλοποιείται μέσω διεπαφών, λάμδα Kotlin και ListenableFuture.

Κύρια σημεία

  • Callback — συνάρτηση επιστροφής κλήσης που μεταβιβάζεται ως όρισμα για ασύγχρονη εκτέλεση.
  • Swift χρησιμοποιεί closures με τη λέξη-κλειδί @escaping για callback.
  • Kotlin εφαρμόζει εκφράσεις λάμδα και συναρτήσεις ανώτερης τάξης για callback.
  • Retain cycle — διαρροή μνήμης κατά τη σύλληψη του self σε callback στο iOS.
  • Callback Hell — πρόβλημα ένθετων callback, που λύνεται μέσω async/await και coroutine.

Τι είναι το Callback;

Callback (συνάρτηση επιστροφής κλήσης) — είναι εκτελέσιμος κώδικας που μεταβιβάζεται σε μια άλλη συνάρτηση και καλείται μετά την ολοκλήρωση μιας συγκεκριμένης ενέργειας. Στην ανάπτυξη εφαρμογών για κινητά, το callback είναι ο θεμελιώδης μηχανισμός ασύγχρονου προγραμματισμού, επιτρέποντας την αντίδραση στην ολοκλήρωση αιτημάτων δικτύου, χρονοδιακοπτών, κινούμενων σχεδίων και λειτουργιών εισόδου/εξόδου χωρίς αποκλεισμό του κύριου νήματος. Η Swift και η Kotlin παρέχουν ενσωματωμένες συντακτικές κατασκευές για τη δημιουργία callback — closures και λάμδα αντίστοιχα.

Αρχή λειτουργίας του Callback

Μια συνάρτηση ανώτερης τάξης δέχεται μια άλλη συνάρτηση ως παράμετρο και την καλεί μετά την εκτέλεση της κύριας λογικής της. Η ροή ελέγχου επιστρέφει στον καλούντα μέσω του callback, εξού και η ονομασία. Στο iOS, το callback εφαρμόζεται στο UIKit (κινούμενα σχέδια UIView.animate), Foundation (URLSession.dataTask) και Combine (sink). Στο Android, το callback χρησιμοποιείται σε View.OnClickListener, Retrofit Callback και Room DAO. Τα σύγχρονα API αντικαθιστούν όλο και περισσότερο το callback με async/await ή coroutine, αλλά η κατανόηση του callback είναι απαραίτητη για εργασία με παλαιό κώδικα και API χαμηλού επιπέδου.

Σύγχρονο και ασύγχρονο Callback

Το callback μπορεί να είναι σύγχρονο (καλείται αμέσως εντός της συνάρτησης) και ασύγχρονο (καλείται αργότερα από άλλο νήμα ή ουρά). Τα σύγχρονα callback χρησιμοποιούνται για ταξινόμηση (συγκριτές) και διάσχιση συλλογών. Ασύγχρονα callback εφαρμόζονται για αιτήματα δικτύου, ανάγνωση αρχείων και εργασία με αισθητήρες. Η διαφορά είναι κρίσιμη για την κατανόηση threading: το σύγχρονο callback εκτελείται στο ίδιο νήμα, το ασύγχρονο — στο νήμα που καθορίζεται από τον διανομέα (DispatchQueue στο iOS, Dispatchers στο Kotlin).

Πώς λειτουργεί το Callback σε iOS και Android;

Ο μηχανισμός callback και στις δύο πλατφόρμες βασίζεται στην ίδια αρχή: η συνάρτηση μεταβιβάζεται ως αντικείμενο πρώτης τάξης και αποθηκεύεται μέχρι τη στιγμή εκτέλεσης. Ωστόσο, οι υλοποιήσεις διαφέρουν λόγω διαφορετικών γλωσσικών παραδειγμάτων. Στο iOS, το callback είναι ένα closure που συλλαμβάνει μεταβλητές από το περιβάλλον πλαίσιο. Στο Android, το callback συνήθως υλοποιείται μέσω ανώνυμων κλάσεων ή εκφράσεων λάμδα Kotlin, που μεταγλωττίζονται σε FunctionalInterface.

Κύκλος ζωής του Callback στο iOS

Κατά την κλήση μιας ασύγχρονης συνάρτησης, το closure αποθηκεύεται στο heap μαζί με τις συλληφθείσες μεταβλητές. Όταν η λειτουργία ολοκληρωθεί, το σύστημα GCD ή OperationQueue τοποθετεί το callback στην κατάλληλη ουρά (main queue ή background queue). Μετά την εκτέλεση, το callback διαγράφεται από τη μνήμη εάν δεν υπάρχουν ισχυρές αναφορές. Capture list ([weak self]) αποτρέπει τη διατήρηση του αντικειμένου μετά την αποδέσμευσή του. Χωρίς capture list, δημιουργείται retain cycle όπου το αντικείμενο και το callback αναφέρονται αμοιβαία το ένα στο άλλο.

swift
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        completion(.success(data))
    }
    task.resume()
}

// Χρήση με [weak self]
fetchData { [weak self] result in
    guard let self else { return }
    switch result {
    case .success(let data):
        self.updateUI(data)
    case .failure(let error):
        self.showError(error)
    }
}

Κύκλος ζωής του Callback στο Android

Στο Android, το callback μεταβιβάζεται μέσω διεπαφής ή λάμδα. Κατά την εκτέλεση μιας ασύγχρονης λειτουργίας μέσω ExecutorService ή coroutine, το callback αποθηκεύεται στη μνήμη μέχρι την ολοκλήρωση της εργασίας στο παρασκήνιο. Τα λάμδα Kotlin μεταγλωττίζονται σε ανώνυμες κλάσεις που συλλαμβάνουν εξωτερικές μεταβλητές. Η απουσία αδύναμων αναφορών στην JVM απαιτεί χειροκίνητη διαχείριση: μηδενισμός του callback στο onDestroy() ή ακύρωση coroutine μέσω Job.cancel(). Το ViewModel και το LiveData λύνουν αυτό το πρόβλημα σε επίπεδο αρχιτεκτονικού στοιχείου.

kotlin
interface Callback<T> {
    fun onSuccess(data: T)
    fun onError(error: Throwable)
}

class Repository {
    fun loadData(callback: Callback<List<User>>) {
        thread {
            try {
                val result = api.fetchUsers()
                runOnUiThread { callback.onSuccess(result) }
            } catch (e: Exception) {
                runOnUiThread { callback.onError(e) }
            }
        }
    }
}

// Χρήση με λάμδα
repository.loadData(object : Callback<List<User>> {
    override fun onSuccess(data: List<User>) { showUsers(data) }
    override fun onError(error: Throwable) { showError(error.message) }
})

Σύνταξη Callback σε Swift και Kotlin

Η σύνταξη του callback καθορίζεται από τις γλωσσικές δυνατότητες εργασίας με συναρτήσεις ως αντικείμενα πρώτης τάξης. Στη Swift, τα closures έχουν συνοπτική σύνταξη με αυτόματα ονόματα ορισμάτων ($0, $1). Στην Kotlin, τα λάμδα υποστηρίζουν επίσης το it για ένα μόνο όρισμα. Οι διαφορές εκδηλώνονται στον χειρισμό σύλληψης μεταβλητών (capture list στη Swift έναντι μεταβλητών αναφορών στην Kotlin) και στην τυποποίηση (Result<Success, Failure> έναντι Result<T>).

Callback στη Swift: closures

Το closure στη Swift είναι ένα αυτοδύναμο μπλοκ κώδικα που μπορεί να μεταβιβαστεί και να χρησιμοποιηθεί σε μια άλλη συνάρτηση. Τα closures μπορεί να είναι καθολικά (ονομασμένα), ένθετα και expression-level. @escaping σηματοδοτεί ένα closure που θα εκτελεστεί μετά την επιστροφή από τη συνάρτηση — αυτή είναι υποχρεωτική απαίτηση για ασύγχρονα callback. Χωρίς @escaping, το closure μπορεί να εκτελεστεί μόνο εντός του σώματος της συνάρτησης. Το trailing closure syntax επιτρέπει τη μεταβίβαση του closure μετά από τις στρογγυλές παρενθέσεις: fetchData { result in ... }.

swift
typealias NetworkResult = (Result<[String: Any], Error>) -> Void

func performRequest(
    url: URL,
    then handler: @escaping NetworkResult
) {
    let task = URLSession.shared.dataTask(with: url) { data, _, error in
        handler(Result {
            guard let json = try JSONSerialization.jsonObject(with: data)
            else { throw NetworkError.invalidData }
            return json as! [String: Any]
        })
    }
    task.resume()
}

performRequest(url: url) { result in
    switch result {
    case .success(let json): process(json)
    case .failure(let error): log(error.localizedDescription)
    }
}

Callback στην Kotlin: λάμδα και συναρτήσεις ανώτερης τάξης

Η Kotlin υποστηρίζει συναρτήσεις ανώτερης τάξης που δέχονται άλλες συναρτήσεις ως παραμέτρους. Callback στην Kotlin μεταβιβάζεται μέσω παραμέτρου τύπου (T) -> Unit ή (T) -> R για τιμή επιστροφής. Οι συναρτήσεις suspend των coroutine Kotlin αντικαθιστούν το callback με σειριακό κώδικα, αλλά το callback παραμένει σε συμβατά με Java API και στο Android SDK (View.setOnClickListener, TextWatcher). Τα λάμδα Kotlin συλλαμβάνουν αυτόματα μεταβλητές val, οι μεταβλητές var απαιτούν μεταβλητά περιτυλίγματα.

kotlin
fun <T, R> processWithCallback(
    input: T,
    transform: (T) -> R,
    onResult: (R) -> Unit
) {
    thread {
        val result = transform(input)
        runOnUiThread { onResult(result) }
    }
}

// Παράδειγμα με λάμδα
processWithCallback(
    input = "Hello",
    transform = { it.length },
    onResult = { length ->
        textView.text = "Length: $length"
    }
)

Retain cycles και διαρροές μνήμης στο Callback

Retain cycle — κατάσταση όπου δύο αντικείμενα διατηρούν ισχυρές αναφορές το ένα στο άλλο, εμποδίζοντας την απελευθέρωσή τους από τον συλλέκτη απορριμμάτων. Στη Swift, retain cycle προκύπτει όταν το viewController συλλαμβάνει ένα closure και το closure συλλαμβάνει το self. Στην Kotlin/Java, διαρροή συμβαίνει όταν το Activity μεταβιβάζει μια εσωτερική κλάση ή λάμδα σε μια μακροχρόνια λειτουργία παρασκηνίου. Σύμφωνα με το WWDC Session 10216 (2024), η λανθασμένη διαχείριση closures είναι η τρίτη πιο συχνή αιτία διαρροών μνήμης σε εφαρμογές iOS.

Retain cycles στη Swift

Η Swift χρησιμοποιεί Automatic Reference Counting (ARC), η οποία απελευθερώνει το αντικείμενο όταν ο μετρητής αναφορών μηδενιστεί. Capture list [weak self] ή [unowned self] στο closure αποτρέπει το retain cycle. Το weak self δημιουργεί μια προαιρετική αναφορά που γίνεται nil κατά την αποδέσμευση του αντικειμένου. Το unowned self υποθέτει ότι το αντικείμενο ζει περισσότερο από το closure — η παραβίαση αυτής της υπόθεσης προκαλεί crash. Η χρήση του weak self ως ασφαλούς προεπιλεγμένης επιλογής συνιστάται.

swift
class DataController {
    var onDataUpdate: ((String) -> Void)?

    func setupCallback() {
        // Retain cycle!
        onDataUpdate = { text in
            self.process(text)
        }

        // Διορθώθηκε: [weak self]
        onDataUpdate = { [weak self] text in
            guard let self else { return }
            self.process(text)
        }
    }

    func process(_ input: String) { }
}

Διαρροές μνήμης στο Android

Στο Android, διαρροή callback συμβαίνει όταν το Activity ή Fragment μεταβιβάζει έναν ακροατή σε ένα στοιχείο singleton (π.χ. EventBus ή Service). WeakReference επιτρέπει στον συλλέκτη απορριμμάτων να απελευθερώσει το Activity, ακόμη κι αν υπάρχει αδύναμη αναφορά σε αυτό. Τα στοιχεία Lifecycle-aware (LiveData, Flow) λύνουν το πρόβλημα αυτόματα. Τα λάμδα Kotlin που συλλαμβάνουν το πλαίσιο Activity μπορούν επίσης να προκαλέσουν διαρροή: το λάμδα διατηρεί σιωπηρά μια αναφορά στο this.

kotlin
class SafeCallbackManager {
    private val listeners = mutableListOf<WeakReference<(String) -> Unit>>()

    fun addListener(callback: (String) -> Unit) {
        listeners.add(WeakReference(callback))
    }

    fun notifyAll(data: String) {
        val iterator = listeners.iterator()
        while (iterator.hasNext()) {
            val ref = iterator.next().get()
            if (ref != null) ref(data)
            else iterator.remove()
        }
    }
}

// Χρήση σε Fragment
manager.addListener { result ->
    // Το WeakReference δεν κρατά το Fragment
    updateUI(result)
}

Callback Hell και τρόποι αντιμετώπισης

Callback Hell (γνωστό και ως Πυραμίδα της Καταστροφής) — κατάσταση όπου πολλαπλά ένθετα callback δημιουργούν μια βαθιά ένθετη δομή κώδικα, δύσκολη στην ανάγνωση και τον εντοπισμό σφαλμάτων. Κάθε επόμενο βήμα απαιτεί αναμονή για την ολοκλήρωση του προηγούμενου, οδηγώντας σε 5-10 επίπεδα ένθεσης. Αυτό το πρόβλημα είναι χαρακτηριστικό για σειριακές ασύγχρονες λειτουργίες: φόρτωση δεδομένων → ανάλυση → αποθήκευση στη βάση δεδομένων → ενημέρωση UI.

Λύσεις στη Swift: async/await

Η Swift 5.5 εισήγαγε ασύγχρονες συναρτήσεις (async/await) που επιτρέπουν τη σύνταξη ασύγχρονου κώδικα σειριακά. AsyncSequence και AsyncStream αντικαθιστούν τις επαναλήψεις που βασίζονται σε callback. Το πλαίσιο Combine παρέχει τελεστές flatMap, merge, combineLatest για σύνθεση ασύγχρονων ροών χωρίς ένθεση. Ωστόσο, το callback παραμένει απαραίτητο για εργασία με API Objective-C και βιβλιοθήκες τρίτων χωρίς υποστήριξη async.

swift
// Ένθετα callback — Callback Hell
loginUser(credentials) { user in
    fetchProfile(user.id) { profile in
        downloadAvatar(profile.avatarUrl) { image in
            cacheImage(image) { success in
                updateUI(user, profile, image)
            }
        }
    }
}

// async/await — λύση
func loadUserExperience() async throws {
    let user = try await loginUser(credentials)
    let profile = try await fetchProfile(user.id)
    let image = try await downloadAvatar(profile.avatarUrl)
    try await cacheImage(image)
    updateUI(user, profile, image)
}

Λύσεις στην Kotlin: coroutine και Flow

Τα coroutine Kotlin αντικαθιστούν το callback με συναρτήσεις suspend με σειριακή εκτέλεση. Flow παρέχει ψυχρές ροές με τελεστές map, flatMapConcat, combine. Το CoroutineScope επιτρέπει την ακύρωση όλων των εκκινημένων coroutine κατά την καταστροφή του στοιχείου. Τα Room, Retrofit και άλλες βιβλιοθήκες Jetpack έχουν ενσωματωμένη υποστήριξη για συναρτήσεις suspend, εξαλείφοντας την ανάγκη για callback σε τυπικές λειτουργίες.

kotlin
// Σειριακά callback — Callback Hell
api.login(credentials) { user ->
    api.fetchProfile(user.id) { profile ->
        api.download(profile.avatarUrl) { bytes ->
            file.save(bytes) { result ->
                textView.text = result.toString()
            }
        }
    }
}

// Coroutine — λύση
suspend fun loadUserData() {
    val user = withContext(Dispatchers.IO) { api.login(credentials) }
    val profile = withContext(Dispatchers.IO) { api.fetchProfile(user.id) }
    val bytes = withContext(Dispatchers.IO) { api.download(profile.avatarUrl) }
    withContext(Dispatchers.IO) { file.save(bytes) }
    textView.text = "Done"
}

Callback vs Delegate: τι να επιλέξουμε;

Callback και Delegate — δύο προσεγγίσεις για ασύγχρονη ειδοποίηση και η επιλογή μεταξύ τους εξαρτάται από αρχιτεκτονικές απαιτήσεις. Το Callback είναι κατάλληλο για εφάπαξ λειτουργίες με ένα αποτέλεσμα. Το Delegate προορίζεται για πολλαπλά συμβάντα με διαφορετικές υπογραφές μεθόδων. Η Apple συνιστά το delegate για σύνθετα πρωτόκολλα με πολλαπλές μεθόδους, το callback — για απλά closures με ένα αποτέλεσμα. Στο Android, το callback αντικαθιστά το delegate στις περισσότερες περιπτώσεις λόγω υποστήριξης λάμδα.

Πότε να επιλέξουμε Callback

Το Callback είναι βέλτιστο για λειτουργίες με ένα μόνο αποτέλεσμα: αίτημα δικτύου, ανάγνωση αρχείου, κινούμενο σχέδιο με μπλοκ ολοκλήρωσης. Πλεονεκτήματα: συνοπτική σύνταξη, χωρίς ξεχωριστό πρωτόκολλο, άμεση σύλληψη πλαισίου. Μειονεκτήματα: πολυπλοκότητα σε πολλαπλά αποτελέσματα (πρόοδος, παύση, ακύρωση), αδυναμία πολλαπλής αποστολής (εάν το callback μπορεί να κληθεί περισσότερες από μία φορές — χρησιμοποιήστε publisher).

Πότε να επιλέξουμε Delegate

Το Delegate είναι κατάλληλο για πρωτόκολλα με πολλαπλές υποχρεωτικές και προαιρετικές μεθόδους: UITableViewDelegate, CLLocationManagerDelegate, συνδέσεις Bluetooth. Πλεονεκτήματα: σαφής τυποποίηση κάθε μεθόδου, τεκμηρίωση μέσω πρωτοκόλλου, υποστήριξη προαιρετικών μεθόδων μέσω @objc optional. Μειονεκτήματα: κώδικας boilerplate, υποχρεωτική αδύναμη αναφορά στο delegate (weak var delegate), πολυπλοκότητα στη σύλληψη πλαισίου.

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

Ποια είναι η διαφορά μεταξύ callback και συνάρτησης ανώτερης τάξης;

Callback είναι μια ειδική περίπτωση συνάρτησης ανώτερης τάξης. Μια συνάρτηση ανώτερης τάξης δέχεται μια άλλη συνάρτηση ως όρισμα ή την επιστρέφει. Callback είναι μια συνάρτηση που μεταβιβάζεται ειδικά για ασύγχρονη εκτέλεση μετά την ολοκλήρωση της λειτουργίας. Όλα τα callback υλοποιούνται μέσω συναρτήσεων ανώτερης τάξης, αλλά δεν είναι κάθε συνάρτηση ανώτερης τάξης callback.

Μπορεί το callback να κληθεί πολλές φορές;

Σύμφωνα με τη σύμβαση, το callback πρέπει να κληθεί ακριβώς μία φορά — είτε success είτε failure. Πολλαπλή κλήση του ίδιου callback θεωρείται σφάλμα σχεδιασμού. Για πολλαπλά συμβάντα (πρόοδος, ροή δεδομένων) χρησιμοποιήστε Observable, Publisher ή Flow — υποστηρίζουν πολλαπλή εκπομπή τιμών. Ορισμένα API παραβιάζουν αυτόν τον κανόνα, οδηγώντας σε δυσνόητα σφάλματα.

Τι είναι το trailing closure στη Swift;

Trailing closure — συντακτική ζάχαρη στη Swift που επιτρέπει τη μεταβίβαση του closure μετά από τις στρογγυλές παρενθέσεις κλήσης συνάρτησης. Εάν η συνάρτηση δέχεται ένα closure ως τελευταίο όρισμα, μπορεί να τοποθετηθεί εκτός παρενθέσεων: fetchData { result in ... }. Για πολλαπλά closures, το trailing closure εφαρμόζεται μόνο στο τελευταίο, τα υπόλοιπα ονομάζονται εντός παρενθέσεων. Αυτό βελτιώνει την αναγνωσιμότητα των API που βασίζονται σε callback.

Πώς να αποφύγουμε διαρροή μνήμης στο callback στο Android;

Χρησιμοποιήστε WeakReference για μακρόβιους ακροατές, ακυρώστε τα coroutine μέσω Job.cancel() στο onDestroy(), εφαρμόστε lifecycleScope για αυτόματη ακύρωση. Το ViewModel + LiveData/Flow λύνουν το πρόβλημα σε αρχιτεκτονικό επίπεδο. Αποφύγετε τη μεταβίβαση πλαισίου Activity σε στατικά callback — χρησιμοποιήστε πλαίσιο Application. Τα λάμδα Kotlin συλλαμβάνουν σιωπηρά το this, ελέγξτε μέσω memory profiler.

Θα αντικαταστήσει το async/await πλήρως το callback;

Async/await αντικαθιστά το callback για σειριακό ασύγχρονο κώδικα, αλλά όχι για αρχιτεκτονική event-driven. Το callback παραμένει σε API συστήματος (View.OnClickListener, εκπρόσωποι URLSession), κλήσεις επιστροφής με πρόοδο και βιβλιοθήκες τρίτων. Η πλήρης αντικατάσταση είναι αδύνατη λόγω αντίστροφης συμβατότητας. Η σύγχρονη στρατηγική είναι η χρήση async/await με περιτυλίγματα callback (continuation στη Swift, suspendCancellableCoroutine στην Kotlin).

Σύνοψη

  • Callback — συνάρτηση επιστροφής κλήσης που μεταβιβάζεται ως όρισμα για ασύγχρονη εκτέλεση μετά την ολοκλήρωση της λειτουργίας.
  • Swift υλοποιεί το callback μέσω closures με @escaping, capture list [weak self] και trailing closure σύνταξη.
  • Kotlin χρησιμοποιεί λάμδα, συναρτήσεις ανώτερης τάξης και συναρτήσεις suspend coroutine για ασύγχρονο προγραμματισμό.
  • Retain cycle στο iOS αποτρέπεται με capture list· στο Android — με WeakReference και στοιχεία Lifecycle-aware.
  • Callback Hell λύνεται μέσω async/await στη Swift και coroutine με Flow στην Kotlin.
  • Delegate είναι προτιμότερο από το callback για πρωτόκολλα με πολλαπλές μεθόδους· callback — για εφάπαξ λειτουργίες.
  • Χρησιμοποιήστε callback για απλές ασύγχρονες λειτουργίες, async/await για σειριακές αλυσίδες, delegate για πολλαπλά συμβάντα.

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

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

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

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