Η υπό όρους μεταγλώττιση επιτρέπει στον μεταγλωττιστή να συμπεριλάβει ή να παραλείψει τμήματα του πηγαίου κώδικα ανάλογα με συνθήκες που είναι γνωστές στη φάση κατασκευής. Σύμφωνα με το The Swift Programming Language (2026), η οδηγία #if επεξεργάζεται στη φάση ανάλυσης AST πριν από τη δημιουργία κώδικα μηχανής. Η υπό όρους μεταγλώττιση δίνει στους προγραμματιστές τη δυνατότητα να διατηρούν μια ενοποιημένη βάση κώδικα για πολλαπλές πλατφόρμες και διαμορφώσεις χωρίς διπλασιασμό.
Κύρια σημεία
Υπό όρους μεταγλώττιση είναι ένας μηχανισμός κατά τον οποίο ο μεταγλωττιστής αναλύει τις οδηγίες μεταγλώττισης υπό όρους και συμπεριλαμβάνει στο δυαδικό αρχείο εξόδου μόνο εκείνα τα τμήματα κώδικα για τα οποία ικανοποιούνται οι συνθήκες. Αυτό επιτρέπει την ύπαρξη μιας ενοποιημένης βάσης κώδικα που προσαρμόζεται σε διαφορετικές πλατφόρμες-στόχους και διαμορφώσεις.
Η ιδέα προέρχεται από τη 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 παρέχει τέσσερις βασικές οδηγίες: #if, #elseif, #else, #endif. Σε αντίθεση με τον προεπεξεργαστή C, το Swift απαιτεί συντακτική ορθότητα κώδικα σε όλους τους κλάδους — ο μεταγλωττιστής αναλύει όλο τον κώδικα, αλλά παράγει κώδικα μηχανής μόνο για ενεργούς κλάδους.
Το Swift υποστηρίζει ενσωματωμένες λειτουργίες ελέγχου: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Αυτές οι λειτουργίες ελέγχουν την πλατφόρμα-στόχο για την οποία κατασκευάζεται η εφαρμογή. Ο συνδυασμός με && και || επιτρέπει τη δημιουργία σύνθετων συνθηκών.
// Ενοποιημένος κώδικας για 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) μπορούν να προστατευτούν με έναν τέτοιο έλεγχο.
// Αντίστροφη συμβατότητα
#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(ModuleName) ελέγχει εάν η καθορισμένη ενότητα είναι διαθέσιμη στο τρέχον περιβάλλον κατασκευής. Αυτός είναι ο πιο ευέλικτος μηχανισμός: δεν συνδέεται με συγκεκριμένη πλατφόρμα. Για παράδειγμα, ο κώδικας που χρησιμοποιεί CoreHaptics θα μεταγλωττιστεί μόνο σε συσκευές όπου αυτό το πλαίσιο είναι διαθέσιμο.
#if canImport(CoreHaptics)
import CoreHaptics
class HapticManager {
private var engine: CHHapticEngine?
func playTapFeedback() {
guard let engine else { return }
// Υλοποίηση απτικής ανάδρασης
}
}
#endif
Η Kotlin ως γλώσσα δεν έχει οδηγίες προεπεξεργαστή. Αντ' αυτού, το οικοσύστημα Android προσφέρει τρεις εναλλακτικές: πεδία BuildConfig (έλεγχοι χρόνου εκτέλεσης), sourceSets (αντικατάσταση ολόκληρων αρχείων) και expect/actual (στο Kotlin Multiplatform).
Τα Gradle sourceSets επιτρέπουν την ύπαρξη διαφορετικών υλοποιήσεων κλάσεων για διαφορετικές γεύσεις ή τύπους κατασκευής. Στον κατάλογο src/debug/ βρίσκεται η υλοποίηση για debug, στο src/release/ — για release. Κατά την κατασκευή, το Gradle επιλέγει το κατάλληλο sourceSet και μεταγλωττίζει μόνο τα αρχεία του.
// 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
}
}
Το KMP παρέχει τον μηχανισμό expect (δήλωση στον κοινό κώδικα) και actual (υλοποίηση για συγκεκριμένη πλατφόρμα). Αυτός είναι ένας μηχανισμός χρόνου μεταγλώττισης: για iOS μεταγλωττίζεται η actual υλοποίηση από το iOS sourceSet, για Android — από το Android sourceSet. Οι μη στοχευμένες υλοποιήσεις δεν μεταγλωττίζονται.
// 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
Κατά την ανάπτυξη εγγενών βιβλιοθηκών μέσω Android NDK, χρησιμοποιείται ο κλασικός προεπεξεργαστής C/C++ με οδηγίες #ifdef, #ifndef, #define. Σε αντίθεση με το Swift, ο προεπεξεργαστής C λειτουργεί σε κειμενικό επίπεδο — ο κώδικας σε ανενεργούς κλάδους μπορεί να είναι συντακτικά λανθασμένος.
Το NDK ορίζει μακροεντολές για κάθε πλατφόρμα: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Για αρχιτεκτονικές: __arm__, __aarch64__, __x86_64__. Αυτές οι μακροεντολές ορίζονται αυτόματα από τον μεταγλωττιστή κατά την κατασκευή για την πλατφόρμα-στόχο.
// Εγγενής κώδικας για 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/Kotlin | BuildConfig.FLAVOR |
Το πιο επικίνδυνο αντ-ιπρότυπο — εξάπλωση οδηγιών σε όλο τον κώδικα. Αν κάθε δεύτερο αρχείο περιέχει #if, αυτό είναι σήμα ότι η αρχιτεκτονική απαιτεί αναδιάρθρωση. Η σωστή λύση — να τοποθετήσετε τον κώδικα πλατφόρμας πίσω από πρωτόκολλα/διεπαφές και να χρησιμοποιήσετε Dependency Injection.
Συχνές Ερωτήσεις
Η υπό όρους μεταγλώττιση λειτουργεί στη φάση μεταγλώττισης: ο ανενεργός κώδικας δεν μπαίνει στο δυαδικό αρχείο. Οι έλεγχοι χρόνου εκτέλεσης (if / switch) μεταγλωττίζονται πάντα, η συνθήκη ελέγχεται κατά την εκτέλεση. Το πρώτο είναι ασφαλέστερο και αποδοτικότερο, το δεύτερο είναι πιο ευέλικτο (μπορεί να αλλάξει χωρίς αναμεταγλώττιση).
Ναι, το Swift επιτρέπει οδηγίες #if μέσα σε συναρτήσεις, βρόχους και ακόμη και μέσα σε εκφράσεις. Αυτή είναι μια από τις δυνατότητες που έλειπαν από τις πρώτες εκδόσεις Swift. Για παράδειγμα: let x = #if DEBUG 1 #else 0 #endif — σωστός κώδικας.
Οι προγραμματιστές της Kotlin εγκατέλειψαν συνειδητά τον προεπεξεργαστή, θεωρώντας τον πηγή εύθραυστου κώδικα. Αντ' αυτού, προσφέρουν expect/actual (ασφάλεια χρόνου μεταγλώττισης) και Gradle sourceSets (απομόνωση σε επίπεδο αρχείου). Και οι δύο προσεγγίσεις είναι πιο αξιόπιστες από την κειμενική αντικατάσταση.
Κατασκευάστε την εφαρμογή με διαφορετικούς συνδυασμούς σημαιών στο CI. Για Swift: ρυθμίστε ξεχωριστά σχήματα Xcode με διαφορετικές Active Compilation Conditions. Για Android: ρυθμίστε ξεχωριστά Build Variants και εκτελέστε δοκιμές για κάθε ένα. Η αυτοματοποίηση είναι υποχρεωτική.
Στο Swift, η συνθήκη #if είναι μια οδηγία μεταγλωττιστή. Αν η ίδια η συνθήκη είναι συντακτικά λανθασμένη (για παράδειγμα, τυπογραφικό λάθος στο όνομα os()), ο μεταγλωττιστής θα εκδώσει σφάλμα μεταγλώττισης. Σε C/C++, ο προεπεξεργαστής απλά δεν θα βρει τη μακροεντολή και η συνθήκη θα γίνει ψευδής.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης