Υπό όρους μεταγλώττιση σε εφαρμογές κινητών — ουσία, οδηγίες και αρχή λειτουργίας

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

Η υπό όρους μεταγλώττιση επιτρέπει στον μεταγλωττιστή να συμπεριλάβει ή να παραλείψει τμήματα του πηγαίου κώδικα ανάλογα με συνθήκες που είναι γνωστές στη φάση κατασκευής. Σύμφωνα με το The Swift Programming Language (2026), η οδηγία #if επεξεργάζεται στη φάση ανάλυσης AST πριν από τη δημιουργία κώδικα μηχανής. Η υπό όρους μεταγλώττιση δίνει στους προγραμματιστές τη δυνατότητα να διατηρούν μια ενοποιημένη βάση κώδικα για πολλαπλές πλατφόρμες και διαμορφώσεις χωρίς διπλασιασμό.

Κύρια σημεία

  • Υπό όρους μεταγλώττιση — τεχνική επιλεκτικής μεταγλώττισης κώδικα βάσει συνθηκών πλατφόρμας, διαμόρφωσης ή έκδοσης γλώσσας.
  • Οδηγίες #if, #elseif, #else, #endif — βασικές δομές υπό όρους μεταγλώττισης σε Swift, C, C++, Objective-C.
  • Kotlin δεν έχει οδηγίες προεπεξεργαστή — αντ' αυτού χρησιμοποιούνται BuildConfig, expect/actual και sourceSets.
  • Πλεονέκτημα — ο κώδικας για ακατάλληλες πλατφόρμες δεν μεταγλωττίζεται, μειώνοντας το μέγεθος του δυαδικού αρχείου και εξαλείφοντας σφάλματα.
  • iOS/macOS κοινός κώδικας — η υπό όρους μεταγλώττιση είναι η βάση ανάπτυξης διασταυρούμενων πλατφορμών πλαισίων της Apple.

Τι είναι η υπό όρους μεταγλώττιση

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

Η ιδέα προέρχεται από τη C/C++ με τις οδηγίες προεπεξεργαστή #ifdef, #ifndef, #endif. Στις σύγχρονες γλώσσες (Swift, Rust, Go) ο μηχανισμός λειτουργεί σε επίπεδο μεταγλωττιστή χωρίς ξεχωριστό προεπεξεργαστή, αυξάνοντας την ασφάλεια: τα υπό όρους τμήματα πρέπει να είναι συντακτικά σωστά, ακόμη και αν δεν μεταγλωττίζονται.

Σύμφωνα με δεδομένα από τη συνεδρία Apple WWDC “Embrace Swift” (2025), περίπου 40% των έργων Swift χρησιμοποιούν υπό όρους μεταγλώττιση για την υποστήριξη iOS και macOS σε έναν στόχο. Για έργα με UIKit και SwiftUI, ο κώδικας UI συχνά διαχωρίζεται με οδηγίες #if os(iOS) και #if os(macOS), επιτρέποντας την επαναχρησιμοποίηση επιχειρηματικής λογικής.

Το κύριο πλεονέκτημα — ασφάλεια κατά τη μεταγλώττιση. Ο κώδικας για μια ακατάλληλη πλατφόρμα όχι μόνο δεν εκτελείται, αλλά ούτε μεταγλωττίζεται. Αυτό σημαίνει ότι τα σφάλματα σε κώδικα ειδικό για iOS δεν θα εμφανιστούν κατά την κατασκευή για macOS και αντίστροφα. Οι έλεγχοι χρόνου εκτέλεσης δεν παρέχουν τέτοια εγγύηση.

Υπό όρους μεταγλώττιση σε Swift

Το Swift παρέχει τέσσερις βασικές οδηγίες: #if, #elseif, #else, #endif. Σε αντίθεση με τον προεπεξεργαστή C, το Swift απαιτεί συντακτική ορθότητα κώδικα σε όλους τους κλάδους — ο μεταγλωττιστής αναλύει όλο τον κώδικα, αλλά παράγει κώδικα μηχανής μόνο για ενεργούς κλάδους.

Έλεγχοι πλατφόρμας os()

Το Swift υποστηρίζει ενσωματωμένες λειτουργίες ελέγχου: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Αυτές οι λειτουργίες ελέγχουν την πλατφόρμα-στόχο για την οποία κατασκευάζεται η εφαρμογή. Ο συνδυασμός με && και || επιτρέπει τη δημιουργία σύνθετων συνθηκών.

swift
// Ενοποιημένος κώδικας για iOS, macOS και tvOS
import Foundation

class PlatformService {
    func getSystemVersion() -> String {
        #if os(iOS) || os(tvOS)
            return UIDevice.current.systemVersion
        #elseif os(macOS)
            let vers = ProcessInfo.processInfo.operatingSystemVersion
            return "\(vers.majorVersion).\(vers.minorVersion)"
        #else
            return "unknown"
        #endif
    }
}

Έλεγχοι έκδοσης μεταγλωττιστή

Το Swift υποστηρίζει τον έλεγχο έκδοσης μεταγλωττιστή: #if swift(>=5.9). Αυτό είναι χρήσιμο για βιβλιοθήκες και πλαίσια που υποστηρίζουν πολλές εκδόσεις Swift. Νέες δυνατότητες γλώσσας (για παράδειγμα, μακροεντολές στο Swift 5.9) μπορούν να προστατευτούν με έναν τέτοιο έλεγχο.

swift
// Αντίστροφη συμβατότητα
#if swift(>=5.9)
    @MainActor
    struct ModernView: View {
        var body: some View {
            Text("Σύγχρονο SwiftUI")
        }
    }
#else
    struct ModernView: View {
        var body: some View {
            Text("Παλαιό SwiftUI")
        }
    }
#endif

Έλεγχος διαθεσιμότητας ενότητας canImport()

Η λειτουργία canImport(ModuleName) ελέγχει εάν η καθορισμένη ενότητα είναι διαθέσιμη στο τρέχον περιβάλλον κατασκευής. Αυτός είναι ο πιο ευέλικτος μηχανισμός: δεν συνδέεται με συγκεκριμένη πλατφόρμα. Για παράδειγμα, ο κώδικας που χρησιμοποιεί CoreHaptics θα μεταγλωττιστεί μόνο σε συσκευές όπου αυτό το πλαίσιο είναι διαθέσιμο.

swift
#if canImport(CoreHaptics)
    import CoreHaptics

    class HapticManager {
        private var engine: CHHapticEngine?

        func playTapFeedback() {
            guard let engine else { return }
            // Υλοποίηση απτικής ανάδρασης
        }
    }
#endif

Εναλλακτικές σε Kotlin και Android

Η Kotlin ως γλώσσα δεν έχει οδηγίες προεπεξεργαστή. Αντ' αυτού, το οικοσύστημα Android προσφέρει τρεις εναλλακτικές: πεδία BuildConfig (έλεγχοι χρόνου εκτέλεσης), sourceSets (αντικατάσταση ολόκληρων αρχείων) και expect/actual (στο Kotlin Multiplatform).

Source Sets στο Gradle

Τα Gradle sourceSets επιτρέπουν την ύπαρξη διαφορετικών υλοποιήσεων κλάσεων για διαφορετικές γεύσεις ή τύπους κατασκευής. Στον κατάλογο src/debug/ βρίσκεται η υλοποίηση για debug, στο src/release/ — για release. Κατά την κατασκευή, το Gradle επιλέγει το κατάλληλο sourceSet και μεταγλωττίζει μόνο τα αρχεία του.

kotlin
// src/debug/kotlin/com/example/Logger.kt
object Logger {
    fun log(tag: String, message: String) {
        Log.d(tag, message)
    }
}

// src/release/kotlin/com/example/Logger.kt
object Logger {
    fun log(tag: String, message: String) {
        // No-op σε release
    }
}

Expect/Actual στο Kotlin Multiplatform

Το KMP παρέχει τον μηχανισμό expect (δήλωση στον κοινό κώδικα) και actual (υλοποίηση για συγκεκριμένη πλατφόρμα). Αυτός είναι ένας μηχανισμός χρόνου μεταγλώττισης: για iOS μεταγλωττίζεται η actual υλοποίηση από το iOS sourceSet, για Android — από το Android sourceSet. Οι μη στοχευμένες υλοποιήσεις δεν μεταγλωττίζονται.

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

// androidMain — actual για Android
actual fun getPlatformName(): String =
    "Android \${Build.VERSION.SDK_INT}"

// iosMain — actual για iOS
actual fun getPlatformName(): String =
    UIDevice.current.systemName() + " " + UIDevice.current.systemVersion

Προεπεξεργαστής C/C++ και NDK

Κατά την ανάπτυξη εγγενών βιβλιοθηκών μέσω Android NDK, χρησιμοποιείται ο κλασικός προεπεξεργαστής C/C++ με οδηγίες #ifdef, #ifndef, #define. Σε αντίθεση με το Swift, ο προεπεξεργαστής C λειτουργεί σε κειμενικό επίπεδο — ο κώδικας σε ανενεργούς κλάδους μπορεί να είναι συντακτικά λανθασμένος.

Σημαίες πλατφόρμας NDK

Το NDK ορίζει μακροεντολές για κάθε πλατφόρμα: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Για αρχιτεκτονικές: __arm__, __aarch64__, __x86_64__. Αυτές οι μακροεντολές ορίζονται αυτόματα από τον μεταγλωττιστή κατά την κατασκευή για την πλατφόρμα-στόχο.

cpp
// Εγγενής κώδικας για Android και iOS
#include <cstdint>

#ifdef __ANDROID__
    int32_t getJniEnv(JNIEnv* env) {
        return env->GetVersion();
    }
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    int32_t getOsVersion() {
        #if TARGET_OS_IOS
            return "iOS";
        #elif TARGET_OS_OSX
            return "macOS";
        #endif
    }
#endif

Κατά την εργασία με NDK, είναι σημαντικό να θυμάστε ότι ο προεπεξεργαστής C/C++ είναι μια κειμενική αντικατάσταση. Εάν σε έναν ανενεργό κλάδο υπάρχει συντακτικό σφάλμα, ο μεταγλωττιστής δεν θα το δει, αλλά εάν λόγω λανθασμένου #define χαλάσει ο ενεργός κλάδος — το σφάλμα θα εμφανιστεί. Συνιστάται η ελαχιστοποίηση αλυσίδων #define και η χρήση constexpr σταθερών.

Για τη Rust, η οποία χρησιμοποιείται επίσης στην ανάπτυξη κινητών μέσω UniFFI και Mozilla Application Services, υπάρχει δικός της μηχανισμός — feature flags στο Cargo.toml. Σημαίες όπως #[cfg(target_os = "android")] στη Rust λειτουργούν παρόμοια με τις οδηγίες Swift: ο έλεγχος γίνεται σε επίπεδο μεταγλωττιστή, όχι προεπεξεργαστή. Αυτό καθιστά τη Rust ελκυστική επιλογή για εγγενείς βιβλιοθήκες που πρέπει να μεταγλωττίζονται για Android και iOS από μια ενιαία βάση κώδικα.

Πρακτικά σενάρια και αντ-ιπρότυπα

Η υπό όρους μεταγλώττιση είναι αποτελεσματική σε αυστηρά καθορισμένα σενάρια. Όταν χρησιμοποιείται λανθασμένα, δημιουργεί κώδικα με οσμή που είναι δύσκολο να δοκιμαστεί και να συντηρηθεί. Ας εξετάσουμε τα σωστά σενάρια και τα τυπικά λάθη.

Σωστά σενάρια

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

ΣενάριοΓλώσσαΣυνθήκη
Αφαίρεση πλατφόρμαςSwift#if os(iOS)
Εντοπισμός σφαλμάτωνSwift/ObjC#if DEBUG
Αντίστροφη συμβατότηταSwift#if swift(>=5.7)
Εγγενής βιβλιοθήκηC/C++#ifdef __ANDROID__
A/B δοκιμήJava/KotlinBuildConfig.FLAVOR

Αντ-ιπρότυπα

Το πιο επικίνδυνο αντ-ιπρότυπο — εξάπλωση οδηγιών σε όλο τον κώδικα. Αν κάθε δεύτερο αρχείο περιέχει #if, αυτό είναι σήμα ότι η αρχιτεκτονική απαιτεί αναδιάρθρωση. Η σωστή λύση — να τοποθετήσετε τον κώδικα πλατφόρμας πίσω από πρωτόκολλα/διεπαφές και να χρησιμοποιήσετε Dependency Injection.

  • #if σε κάθε αρχείο — αρχιτεκτονικό αντ-ιπρότυπο. Ο κώδικας πλατφόρμας πρέπει να απομονώνεται πίσω από πρωτόκολλα.
  • Ένθετα #if — γίνονται γρήγορα δυσανάγνωστα. Το βάθος ένθεσης δεν πρέπει να υπερβαίνει τα 2 επίπεδα.
  • Αντιγραφή ολόκληρων συναρτήσεων — αν μια συνάρτηση έχει αντιγραφεί πλήρως σε #if και #else, πρέπει να μεταφερθεί σε κοινό τμήμα.
  • Δοκιμή — ο κώδικας εντός ανενεργών κλάδων δεν δοκιμάζεται. Απαιτούνται CI κατασκευές όλων των πιθανών συνδυασμών.
  • Μαγικές σημαίες — μη τεκμηριωμένες σημαίες για τις οποίες δεν γνωρίζει η νέα ομάδα προγραμματιστών.

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

Σε τι διαφέρει η υπό όρους μεταγλώττιση από τους ελέγχους χρόνου εκτέλεσης;

Η υπό όρους μεταγλώττιση λειτουργεί στη φάση μεταγλώττισης: ο ανενεργός κώδικας δεν μπαίνει στο δυαδικό αρχείο. Οι έλεγχοι χρόνου εκτέλεσης (if / switch) μεταγλωττίζονται πάντα, η συνθήκη ελέγχεται κατά την εκτέλεση. Το πρώτο είναι ασφαλέστερο και αποδοτικότερο, το δεύτερο είναι πιο ευέλικτο (μπορεί να αλλάξει χωρίς αναμεταγλώττιση).

Μπορεί να χρησιμοποιηθεί το #if μέσα σε μια συνάρτηση σε Swift;

Ναι, το Swift επιτρέπει οδηγίες #if μέσα σε συναρτήσεις, βρόχους και ακόμη και μέσα σε εκφράσεις. Αυτή είναι μια από τις δυνατότητες που έλειπαν από τις πρώτες εκδόσεις Swift. Για παράδειγμα: let x = #if DEBUG 1 #else 0 #endif — σωστός κώδικας.

Γιατί η Kotlin δεν πρόσθεσε προεπεξεργαστή;

Οι προγραμματιστές της Kotlin εγκατέλειψαν συνειδητά τον προεπεξεργαστή, θεωρώντας τον πηγή εύθραυστου κώδικα. Αντ' αυτού, προσφέρουν expect/actual (ασφάλεια χρόνου μεταγλώττισης) και Gradle sourceSets (απομόνωση σε επίπεδο αρχείου). Και οι δύο προσεγγίσεις είναι πιο αξιόπιστες από την κειμενική αντικατάσταση.

Πώς να δοκιμάσουμε κώδικα εντός ανενεργών κλάδων #if;

Κατασκευάστε την εφαρμογή με διαφορετικούς συνδυασμούς σημαιών στο CI. Για Swift: ρυθμίστε ξεχωριστά σχήματα Xcode με διαφορετικές Active Compilation Conditions. Για Android: ρυθμίστε ξεχωριστά Build Variants και εκτελέστε δοκιμές για κάθε ένα. Η αυτοματοποίηση είναι υποχρεωτική.

Τι συμβαίνει αν η συνθήκη #if περιέχει συντακτικό σφάλμα;

Στο Swift, η συνθήκη #if είναι μια οδηγία μεταγλωττιστή. Αν η ίδια η συνθήκη είναι συντακτικά λανθασμένη (για παράδειγμα, τυπογραφικό λάθος στο όνομα os()), ο μεταγλωττιστής θα εκδώσει σφάλμα μεταγλώττισης. Σε C/C++, ο προεπεξεργαστής απλά δεν θα βρει τη μακροεντολή και η συνθήκη θα γίνει ψευδής.

Σύνοψη

  • Υπό όρους μεταγλώττιση — τεχνική μεταγλώττισης που αποκλείει μη στοχευμένο κώδικα στη φάση κατασκευής, σε αντίθεση με τους ελέγχους χρόνου εκτέλεσης.
  • Swift υποστηρίζει #if με os(), canImport(), swift() — οδηγίες ασφαλείς κατά τη μεταγλώττιση που απαιτούν συντακτική ορθότητα όλων των κλάδων.
  • Kotlin χρησιμοποιεί expect/actual και Gradle sourceSets αντί προεπεξεργαστή — πιο αξιόπιστες αλλά λιγότερο ευέλικτες προσεγγίσεις.
  • C/C++ σε NDK χρησιμοποιεί κλασικό κειμενικό προεπεξεργαστή #ifdef / #ifndef με μακροεντολές πλατφόρμας __ANDROID__, __APPLE__.
  • Σωστή εφαρμογή — αφαίρεση πλατφόρμας, εντοπισμός σφαλμάτων, αντίστροφη συμβατότητα. Λανθασμένη — #if σε κάθε αρχείο, βαθιά ένθεση, μαγικές σημαίες.
  • CI υποχρεωτικό — όλοι οι συνδυασμοί σημαιών πρέπει να κατασκευάζονται και να δοκιμάζονται αυτόματα, διαφορετικά ο κώδικας σε ανενεργούς κλάδους γίνεται νεκρός.

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

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

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

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