expect/actual — ουσία, λέξεις-κλειδιά KMM και πώς λειτουργούν

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

expect/actual — μηχανισμός του Kotlin Multiplatform που επιτρέπει τη δήλωση πλατφορμο-εξαρτημένων API σε κοινό κώδικα. Η λέξη-κλειδί expect δημιουργεί μια σύμβαση συνάρτησης, κλάσης ή ιδιότητας στο commonMain, ενώ η λέξη-κλειδί actual παρέχει τη συγκεκριμένη υλοποίηση για κάθε πλατφόρμα. Ο μεταγλωττιστής ελέγχει ότι κάθε expect-δήλωση αντιστοιχεί σε actual-υλοποίηση σε όλες τις πλατφόρμες-στόχους. Σύμφωνα με τα δεδομένα του JetBrains, 2025, ο μηχανισμός χρησιμοποιείται στο 80% των KMM-έργων για την υλοποίηση πλατφορμικής επιχειρηματικής λογικής.

Κύρια σημεία

  • expect — λέξη-κλειδί για τη δήλωση μιας σύμβασης συνάρτησης, κλάσης ή ιδιότητας σε κοινό κώδικα.
  • actual — λέξη-κλειδί για την παροχή πλατφορμικής υλοποίησης μιας expect-δήλωσης.
  • commonMain — source set με κοινό κώδικα όπου βρίσκονται οι expect-δηλώσεις.
  • Έλεγχος μεταγλωττιστή — ο μεταγλωττιστής εγγυάται την ύπαρξη actual-υλοποιήσεων για όλες τις πλατφόρμες-στόχους.
  • Source set — σύνολα (iosMain, androidMain) όπου βρίσκονται οι πλατφορμικές actual-υλοποιήσεις.

Τι είναι το expect/actual;

expect/actual — είναι ένας δηλωτικός μηχανισμός του Kotlin Multiplatform για την υλοποίηση πλατφορμο-κατευθυνόμενου προγραμματισμού. Επιτρέπει την περιγραφή ενός API μία φορά στο κοινό module (expect) και την υλοποίησή του ξεχωριστά για κάθε πλατφόρμα (actual). Σε αντίθεση με τις διεπαφές, το expect/actual δεν δημιουργεί εικονικές κλήσεις — ο μεταγλωττιστής συνδέει τις expect- και actual-δηλώσεις στο στάδιο της μεταγλώττισης, αποκλείοντας τα επιπλέον κόστη δυναμικής δρομολόγησης.

Η ιστορία του expect/actual ξεκίνησε με την εμφάνιση του Kotlin Multiplatform το 2017. Αρχικά ο μηχανισμός ονομαζόταν expect/actual declarations και ήταν πειραματικός. Στο Kotlin 1.2 προστέθηκαν expect-σημειώσεις, και στο Kotlin 1.3 το expect/actual έγινε σταθερό για κλάσεις και συναρτήσεις. Σταδιακά ο μηχανισμός επεκτάθηκε: στο Kotlin 1.6 προστέθηκε υποστήριξη για expect/actual σε companion-αντικείμενα, στο Kotlin 1.7 — για enum-κλάσεις, και στο Kotlin 2.0 — για typealias.

Το βασικό χαρακτηριστικό του expect/actual είναι η ασφάλεια σε επίπεδο μεταγλώττισης. Αν ένας προγραμματιστής προσθέσει μια expect-δήλωση στο commonMain αλλά ξεχάσει να παράσχει actual-υλοποίηση για iOS, ο μεταγλωττιστής θα δώσει σφάλμα. Αυτό αποτρέπει σφάλματα χρόνου εκτέλεσης, χαρακτηριστικά για προσεγγίσεις με αντανάκλαση (reflection) ή δυναμική φόρτωση πλατφορμικού κώδικα.

Πώς λειτουργεί ο μηχανισμός expect/actual

Ο μηχανισμός expect/actual λειτουργεί σε επίπεδο source set — το σύστημα μονάδων του Kotlin Multiplatform. Ο κοινός κώδικας, προσβάσιμος σε όλες τις πλατφόρμες, βρίσκεται στο source set commonMain. Ο πλατφορμο-εξαρτημένος κώδικας — στα iosMain, androidMain, macosMain και ούτω καθεξής. Η λέξη-κλειδί expect στο commonMain δηλώνει ένα API, ενώ η λέξη-κλειδί actual στο πλατφορμικό source set παρέχει την υλοποίηση. Ο μεταγλωττιστής τα συνδέει στο στάδιο της παραγωγής κώδικα, αντικαθιστώντας την κλήση της expect-συνάρτησης με την αντίστοιχη actual-υλοποίηση για την πλατφόρμα-στόχο.

Η ιεραρχία source set σε ένα τυπικό KMM-έργο φαίνεται ως εξής: commonMain περιέχει expect-δηλώσεις, iosMain και androidMain περιέχουν actual-υλοποιήσεις. Κατά τη μεταγλώττιση για iOS χρησιμοποιείται το actual από το iosMain, κατά τη μεταγλώττιση για Android — από το androidMain. Τα source set μπορεί να είναι ενδιάμεσα (π.χ. iosArm64Main για συγκεκριμένη αρχιτεκτονική), επιτρέποντας την εξειδίκευση υλοποιήσεων για διαφορετικές συσκευές.

kotlin
// commonMain — δήλωση expect
expect fun getPlatformName(): String

// androidMain — actual για Android
actual fun getPlatformName(): String = "Android"

// iosMain — actual για iOS
actual fun getPlatformName(): String = "iOS"

Έλεγχος μεταγλωττιστή για actual-υλοποιήσεις

Ο μεταγλωττιστής Kotlin ελέγχει αρκετές προϋποθέσεις κατά την εργασία με expect/actual. Κάθε expect-δήλωση πρέπει να έχει actual-υλοποίηση για κάθε ενεργή πλατφόρμα. Η υπογραφή της actual-δήλωσης πρέπει να ταιριάζει με την expect-υπογραφή (η σημείωση @OptionalExpectation μπορεί να χαλαρώνει αυτή την απαίτηση). Οι τροποποιητές πρόσβασης, ο τύπος επιστροφής και οι παράμετροι πρέπει να είναι πανομοιότυποι. Ο μεταγλωττιστής επίσης ελέγχει την απουσία κυκλικών εξαρτήσεων μεταξύ expect- και actual-δηλώσεων.

Τύποι expect/actual: συναρτήσεις, κλάσεις, ιδιότητες

Το expect/actual υποστηρίζει διάφορους τύπους δηλώσεων. Οι πιο συχνά χρησιμοποιούμενοι είναι οι expect/actual-συναρτήσεις για πλατφορμικές λειτουργίες, οι expect/actual-κλάσεις για αντικείμενα που απαιτούν εγγενή υλοποίηση, και οι expect/actual-ιδιότητες για σταθερές και ρυθμίσεις. Κάθε τύπος έχει τους δικούς του κανόνες χρήσης και περιορισμούς.

Οι expect/actual-συναρτήσεις — ο απλούστερος και πιο διαδεδομένος τύπος. Χρησιμοποιούνται για κλήση πλατφορμικών API, όπως λήψη ώρας, ανάγνωση αρχείων ή αποστολή HTTP-αιτημάτων. Οι expect/actual-κλάσεις εφαρμόζονται για δημιουργία αντικειμένων που αλληλεπιδρούν άμεσα με εγγενή κώδικα (π.χ. για πρόσβαση σε κάμερα, γεωτοποθεσία ή ασφαλές αποθηκευτικό χώρο). Οι expect/actual-ιδιότητες (val) είναι κατάλληλες για πλατφορμικές σταθερές — όνομα ΛΣ, έκδοση SDK ή διαδρομή συστήματος.

Τύπος δήλωσηςΛέξεις-κλειδιάΠαράδειγμα χρήσης
Συνάρτησηexpect fun / actual funΛήψη μοναδικού αναγνωριστικού συσκευής
Κλάσηexpect class / actual classΠρόσβαση σε SecureStorage (Keychain / EncryptedSharedPreferences)
Ιδιότηταexpect val / actual valΤρέχουσα πλατφόρμα (iOS / Android)
Enum-κλάσηexpect enum / actual enumΛίστα διαθέσιμων αδειών εφαρμογής
Typealiasexpect typealias / actual typealiasΤύπος δικτυακής απόκρισης, ειδικός για πλατφόρμα

Περιορισμοί του expect/actual

Δεν μπορούν όλες οι κατασκευές της Kotlin να χρησιμοποιηθούν με expect/actual. Η expect-δήλωση δεν μπορεί να περιέχει σώμα — μόνο υπογραφή. Η expect-κλάση δεν μπορεί να έχει constructor με παραμέτρους (πρέπει να έχει κενό primary constructor). Για enum expect/actual όλες οι σταθερές πρέπει να είναι ίδιες σε expect και actual. Οι expect-ιδιότητες πρέπει να είναι val (όχι var), καθώς η αποθήκευση κατάστασης στο κοινό module για πλατφορμικές ιδιότητες δεν έχει νόημα.

Παραδείγματα κώδικα: από απλό σε σύνθετο

Ας εξετάσουμε πρακτικά παραδείγματα expect/actual από απλές συναρτήσεις έως πλήρεις κλάσεις. Η βασική περίπτωση — λήψη του ονόματος πλατφόρμας για χρήση στο UI. Πιο σύνθετα παραδείγματα περιλαμβάνουν πρόσβαση σε εγγενή αποθηκευτικό χώρο και εργασία με πλατφορμικά νήματα.

kotlin
// commonMain — κλάση expect για ασφαλή αποθήκευση
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

// androidMain — actual στο Android
actual class PlatformStorage {
    private val prefs = AppContext.getSharedPreferences("secure", 0)

    actual fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() }
    actual fun get(key: String): String? = prefs.getString(key, null)
    actual fun remove(key: String) { prefs.edit().remove(key).apply() }
}

Σε αυτό το παράδειγμα η expect-κλάση PlatformStorage ορίζει μια σύμβαση απλού αποθηκευτικού χώρου κλειδιού-τιμής. Στο Android η υλοποίηση χρησιμοποιεί SharedPreferences, ενώ στο iOS — Keychain ή NSUserDefaults. Χάρη στο expect/actual, η επιχειρηματική λογική στο commonMain καλεί save/get/remove, χωρίς να γνωρίζει την πλατφορμική υλοποίηση.

kotlin
// iosMain — actual στο iOS με Keychain
actual class PlatformStorage {
    actual fun save(key: String, value: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecValueData to value.encodeToByteArray()
        )
        SecItemAdd(query, null)
    }

    actual fun get(key: String): String? {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecReturnData to true
        )
        val result = mutableMapOf<String, Any>()
        return if (SecItemCopyMatching(query, result) == errSecSuccess)
            result[kSecValueData]?.toString()
        else null
    }

    actual fun remove(key: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key
        )
        SecItemDelete(query)
    }
}

Βέλτιστες πρακτικές expect/actual

Κατά τον σχεδιασμό ενός expect/actual API θα πρέπει να ακολουθούνται αρκετές αρχές. Ελαχιστοποιήστε τον αριθμό expect-δηλώσεων — όσο περισσότερος κοινός κώδικας, τόσο πιο εύκολη η συντήρηση. Χρησιμοποιήστε expect/actual μόνο για εκείνα τα API που πραγματικά διαφέρουν στις πλατφόρμες. Για τον υπόλοιπο κώδικα εφαρμόστε διεπαφές με εργοστάσια ή dependency injection, που απλοποιεί τη δοκιμή.

Συνιστάται η ομαδοποίηση expect-δηλώσεων κατά θεματικές ενότητες, αντί να αναμιγνύονται σε ένα αρχείο. Για παράδειγμα, Storage.kt για expect-δηλώσεις αποθηκευτικού χώρου, Platform.kt για expect-συναρτήσεις εργασίας με το ΛΣ και Analytics.kt για expect-κλάσεις αναλυτικής. Αυτό απλοποιεί την πλοήγηση και την κατανόηση της πλατφορμικής επιφάνειας ενός KMM-έργου. Κάθε actual-αρχείο πρέπει να βρίσκεται στο αντίστοιχο source set: androidMain, iosMain, desktopMain και ούτω καθεξής.

Οι προεπιλεγμένες υλοποιήσεις μέσω expect fun με actual fun, όπου το actual χρησιμοποιεί common-κώδικα, είναι ένα συνηθισμένο αντι-μοτίβο. Αν η πλατφορμική υλοποίηση δεν διαφέρει από την προεπιλεγμένη, το expect/actual δεν είναι απαραίτητο. Σε τέτοιες περιπτώσεις χρησιμοποιήστε μια απλή συνάρτηση στο commonMain. Επίσης αποφεύγετε το expect/actual για τετριμμένες μεθόδους λήψης (getters) — χρησιμοποιήστε expect val με σταθερές.

Οργάνωση κώδικα στο έργο

Η σωστή δομή του expect/actual κώδικα είναι κρίσιμη για την αναγνωσιμότητα του έργου. Κάθε expect/actual module πρέπει να έχει ένα ενιαίο σημείο εισόδου. Παράδειγμα οργάνωσης: commonMain/kotlin/com/project/platform περιέχει expect-δηλώσεις, androidMain/kotlin/com/project/platform — actual για Android, iosMain/kotlin/com/project/platform — actual για iOS. Τα ονόματα αρχείων και πακέτων πρέπει να ταιριάζουν για expect και actual, ώστε ο προγραμματιστής να μπορεί να βρει γρήγορα την αντίστοιχη υλοποίηση.

Εναλλακτικές του expect/actual στο KMM

Οι διεπαφές με πλατφορμικό εργοστάσιο — η κύρια εναλλακτική του expect/actual. Αντί για expect-κλάση μπορείτε να δηλώσετε μια διεπαφή στο commonMain, και να δημιουργήσετε συγκεκριμένες κλάσεις στα πλατφορμικά modules. Ένα εργοστάσιο ή ένα container dependency injection παρέχει τη σωστή υλοποίηση κατά τον χρόνο εκτέλεσης. Αυτή η προσέγγιση είναι καλύτερη για δοκιμές, καθώς η διεπαφή μπορεί να υποκατασταθεί (mock).

Dependency Injection (Koin, Kodein) — μια πιο ευέλικτη αλλά λιγότερο αποδοτική προσέγγιση. Το DI-container ρυθμίζεται ξεχωριστά για κάθε πλατφόρμα και παρέχει πλατφορμικές εξαρτήσεις στον κοινό κώδικα. Σε αντίθεση με το expect/actual, η έγχυση γίνεται κατά τον χρόνο εκτέλεσης, δίνοντας τη δυνατότητα αλλαγής υλοποιήσεων για δοκιμές. Από την άλλη πλευρά, σφάλματα διαμόρφωσης DI εντοπίζονται μόνο κατά την εκκίνηση, όχι στο στάδιο της μεταγλώττισης.

ΠροσέγγισηΈλεγχος κατά τη μεταγλώττισηΕυελιξία δοκιμώνΕπιπλέον κόστος χρόνου εκτέλεσης
expect/actualΠλήρηςΧαμηλή (actual δεν μπορεί να υποκατασταθεί)Μηδενικό (σύνδεση στη μεταγλώττιση)
Διεπαφές + εργοστάσιοΜερικήΥψηλή (μπορεί να υποκατασταθεί)Ελάχιστο (εικονική κλήση)
Dependency InjectionΌχι (χρόνος εκτέλεσης)ΥψηλήΜέτριο (DI-proxy)

Η επιλογή μεταξύ expect/actual και εναλλακτικών εξαρτάται από το πλαίσιο. Για κρίσιμη απόδοση (μηχανές παιχνιδιών, επεξεργασία πραγματικού χρόνου) το expect/actual είναι προτιμότερο λόγω μηδενικών επιπλέον εξόδων. Για επιχειρηματική λογική (αποθετήρια, use-cases) είναι καλύτερο να χρησιμοποιούνται διεπαφές με DI για απλοποίηση δοκιμών. Η συνδυασμένη προσέγγιση — expect/actual για χαμηλού επιπέδου πλατφορμικές λειτουργίες και διεπαφές για το επίπεδο επιχειρηματικής λογικής — εφαρμόζεται στα περισσότερα KMM-έργα παραγωγής.

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

Ποια είναι η διαφορά μεταξύ expect/actual και διεπαφών;

Το expect/actual συνδέει την υλοποίηση στο στάδιο της μεταγλώττισης χωρίς εικονικές κλήσεις, ενώ οι διεπαφές — κατά τον χρόνο εκτέλεσης. Το expect/actual εγγυάται την ύπαρξη υλοποίησης για όλες τις πλατφόρμες, οι διεπαφές απαιτούν ελέγχους χρόνου εκτέλεσης.

Μπορώ να χρησιμοποιήσω expect/actual για enum;

Ναι, το expect enum υποστηρίζεται από το Kotlin 1.7 και μετά. Όλες οι σταθερές στο expect και actual enum πρέπει να ταιριάζουν. Διαφορετικές τιμές σταθερών σε διαφορετικές πλατφόρμες αποτελούν σφάλμα μεταγλώττισης.

Τι θα συμβεί αν ξεχάσω μια actual-υλοποίηση;

Ο μεταγλωττιστής θα δώσει σφάλμα για κάθε πλατφόρμα όπου λείπει η actual-υλοποίηση. Το έργο δεν θα μεταγλωττιστεί μέχρι να προστεθούν οι αντίστοιχες actual-υλοποιήσεις για όλες τις expect-δηλώσεις.

Μπορώ να χρησιμοποιήσω expect/actual μέσα σε ένα source set;

Όχι, το expect και το actual πρέπει να βρίσκονται σε διαφορετικά source set. Το expect — στο commonMain ή σε ενδιάμεσο source set, το actual — στο πλατφορμικό source set. Η τοποθέτηση expect και actual στο ίδιο source set αποτελεί σφάλμα μεταγλώττισης.

Πώς να δοκιμάσω expect/actual κώδικα;

Για δοκιμή expect/actual χρησιμοποιήστε commonTest με πλατφορμικά test source set. Γράψτε expect-δοκιμές στο commonTest και actual-δοκιμές για κάθε πλατφόρμα. Οι δοκιμές ολοκλήρωσης εκτελούνται ξεχωριστά σε κάθε πλατφόρμα-στόχο.

Σύνοψη

  • expect/actual — βασικός μηχανισμός του Kotlin Multiplatform για πλατφορμικές υλοποιήσεις με έλεγχο μεταγλωττιστή.
  • expect δηλώνει μια σύμβαση στο commonMain, actual παρέχει υλοποίηση στο πλατφορμικό source set.
  • Οι τύποι δηλώσεων περιλαμβάνουν συναρτήσεις, κλάσεις, ιδιότητες, enum-κλάσεις και typealias με διαφορετικούς κανόνες χρήσης.
  • Ο έλεγχος μεταγλωττιστή εγγυάται την ύπαρξη actual-υλοποιήσεων για όλες τις πλατφόρμες-στόχους, αποτρέποντας σφάλματα χρόνου εκτέλεσης.
  • Συνιστάται η ελαχιστοποίηση expect/actual και η χρήση διεπαφών με DI για επιχειρηματική λογική.
  • Η οργάνωση κώδικα πρέπει να είναι ομοιόμορφη με αντίστοιχα ονόματα αρχείων και πακέτων για expect και actual.
  • Χρησιμοποιήστε expect/actual για χαμηλού επιπέδου πλατφορμικές λειτουργίες (αποθηκευτικός χώρος, σύστημα αρχείων, αισθητήρες) — εξασφαλίζει μηδενικά επιπλέον κόστη χρόνου εκτέλεσης.

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

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

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

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