Kompilasi bersyarat memungkinkan kompiler untuk menyertakan atau melewatkan bagian dari kode sumber tergantung pada kondisi yang diketahui pada tahap pembangunan. Menurut The Swift Programming Language (2026), direktif #if diproses pada tahap analisis AST sebelum pembuatan kode mesin. Kompilasi bersyarat memberi pengembang kemampuan untuk mempertahankan basis kode tunggal untuk beberapa platform dan konfigurasi tanpa duplikasi.
Poin Utama
Kompilasi bersyarat adalah mekanisme di mana kompiler menganalisis direktif kompilasi bersyarat dan menyertakan dalam file biner keluaran hanya blok kode yang kondisinya terpenuhi. Ini memungkinkan memiliki basis kode tunggal yang beradaptasi dengan berbagai platform target dan konfigurasi.
Konsep ini berasal dari C/C++ dengan direktif preprosesor #ifdef, #ifndef, #endif. Dalam bahasa modern (Swift, Rust, Go) mekanisme ini bekerja pada tingkat kompiler tanpa preprosesor terpisah, yang meningkatkan keamanan: blok bersyarat harus benar secara sintaksis, bahkan jika tidak dikompilasi.
Menurut data sesi Apple WWDC “Embrace Swift” (2025), sekitar 40% proyek Swift menggunakan kompilasi bersyarat untuk mendukung iOS dan macOS dalam satu target. Untuk proyek dengan UIKit dan SwiftUI, kode UI sering dipisahkan dengan direktif #if os(iOS) dan #if os(macOS), yang memungkinkan penggunaan kembali logika bisnis.
Keuntungan utama — keamanan waktu kompilasi. Kode untuk platform yang tidak sesuai tidak hanya tidak dijalankan, tetapi juga tidak dikompilasi. Ini berarti kesalahan dalam kode spesifik iOS tidak akan muncul saat membangun untuk macOS dan sebaliknya. Pemeriksaan runtime tidak memberikan jaminan seperti itu.
Swift menyediakan empat direktif utama: #if, #elseif, #else, #endif. Berbeda dengan preprosesor C, Swift memerlukan kebenaran sintaksis kode di semua cabang — kompiler mengurai semua kode, tetapi menghasilkan kode mesin hanya untuk cabang aktif.
Swift mendukung fungsi pemeriksaan bawaan: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Fungsi-fungsi ini memeriksa platform target tempat aplikasi dibangun. Kombinasi dengan && dan || memungkinkan pembuatan kondisi yang kompleks.
// Kode terpadu untuk iOS, macOS, dan 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 mendukung pemeriksaan versi kompiler: #if swift(>=5.9). Ini berguna untuk pustaka dan framework yang mendukung beberapa versi Swift. Fitur bahasa baru (misalnya, makro di Swift 5.9) dapat dilindungi dengan pemeriksaan semacam itu.
// Kompatibilitas mundur
#if swift(>=5.9)
@MainActor
struct ModernView: View {
var body: some View {
Text("SwiftUI Modern")
}
}
#else
struct ModernView: View {
var body: some View {
Text("SwiftUI Lama")
}
}
#endif
Fungsi canImport(ModuleName) memeriksa apakah modul yang ditentukan tersedia di lingkungan build saat ini. Ini adalah mekanisme yang paling fleksibel: tidak terikat pada platform tertentu. Misalnya, kode yang menggunakan CoreHaptics hanya akan dikompilasi pada perangkat di mana framework ini tersedia.
#if canImport(CoreHaptics)
import CoreHaptics
class HapticManager {
private var engine: CHHapticEngine?
func playTapFeedback() {
guard let engine else { return }
// Implementasi umpan balik taktil
}
}
#endif
Kotlin sebagai bahasa tidak memiliki direktif preprosesor. Sebagai gantinya, ekosistem Android menawarkan tiga alternatif: bidang BuildConfig (pemeriksaan runtime), sourceSets (penggantian seluruh file), dan expect/actual (di Kotlin Multiplatform).
Gradle sourceSets memungkinkan memiliki implementasi kelas yang berbeda untuk varian atau tipe build yang berbeda. Di direktori src/debug/ terdapat implementasi untuk debug, di src/release/ — untuk release. Saat membangun, Gradle memilih sourceSet yang sesuai dan hanya mengkompilasi file-file-nya.
// 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 di release
}
}
KMP menyediakan mekanisme expect (deklarasi di kode bersama) dan actual (implementasi untuk platform tertentu). Ini adalah mekanisme waktu kompilasi: untuk iOS, implementasi actual dari iOS sourceSet dikompilasi, untuk Android — dari Android sourceSet. Implementasi yang tidak ditargetkan tidak dikompilasi.
// commonMain — deklarasi expect
expect fun getPlatformName(): String
// androidMain — actual untuk Android
actual fun getPlatformName(): String =
"Android \${Build.VERSION.SDK_INT}"
// iosMain — actual untuk iOS
actual fun getPlatformName(): String =
UIDevice.current.systemName() + " " + UIDevice.current.systemVersion
Dalam pengembangan pustaka native melalui Android NDK, preprosesor C/C++ klasik digunakan dengan direktif #ifdef, #ifndef, #define. Berbeda dengan Swift, preprosesor C bekerja pada tingkat tekstual — kode di cabang tidak aktif mungkin tidak benar secara sintaksis.
NDK mendefinisikan makro untuk setiap platform: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Untuk arsitektur: __arm__, __aarch64__, __x86_64__. Makro ini diatur secara otomatis oleh kompiler saat membangun untuk platform target.
// Kode native untuk Android dan 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
Saat bekerja dengan NDK, penting untuk diingat bahwa preprosesor C/C++ adalah penggantian tekstual. Jika ada kesalahan sintaksis di cabang tidak aktif, kompiler tidak akan melihatnya, tetapi jika karena #define yang salah cabang aktif rusak — kesalahan akan muncul. Disarankan untuk meminimalkan rantai #define dan menggunakan konstanta constexpr.
Untuk Rust, yang juga digunakan dalam pengembangan seluler melalui UniFFI dan Mozilla Application Services, ada mekanisme sendiri — feature flags di Cargo.toml. Bendera seperti #[cfg(target_os = "android")] di Rust bekerja mirip dengan direktif Swift: pemeriksaan dilakukan pada tingkat kompiler, bukan preprosesor. Ini membuat Rust menjadi pilihan menarik untuk pustaka native yang harus dikompilasi untuk Android dan iOS dari satu basis kode.
Kompilasi bersyarat efektif dalam skenario yang ditentukan secara ketat. Jika digunakan secara tidak benar, ia menciptakan kode berbau yang sulit diuji dan dipelihara. Mari kita lihat skenario yang benar dan kesalahan umum.
Skenario pertama — abstraksi platform: fasad tunggal, di mana kompilasi bersyarat memilih implementasi platform. Kedua — debugging dan profiling: alat pengembang yang tidak boleh masuk ke rilis. Ketiga — kompatibilitas mundur: dukungan untuk versi sistem operasi lama hingga versi minimum diperbarui.
| Skenario | Bahasa | Kondisi |
|---|---|---|
| Abstraksi platform | Swift | #if os(iOS) |
| Debugging | Swift/ObjC | #if DEBUG |
| Kompatibilitas mundur | Swift | #if swift(>=5.7) |
| Pustaka native | C/C++ | #ifdef __ANDROID__ |
| Pengujian A/B | Java/Kotlin | BuildConfig.FLAVOR |
Anti-pola yang paling berbahaya — penyebaran direktif di seluruh kode. Jika setiap file kedua berisi #if, itu adalah sinyal bahwa arsitektur memerlukan refactoring. Solusi yang tepat — memisahkan kode platform di belakang protokol/antarmuka dan menggunakan Dependency Injection.
Pertanyaan yang Sering Diajukan
Kompilasi bersyarat bekerja pada tahap kompilasi: kode tidak aktif tidak masuk ke biner. Pemeriksaan runtime (if / switch) selalu dikompilasi, kondisi diperiksa saat eksekusi. Yang pertama lebih aman dan efisien, yang kedua lebih fleksibel (dapat diubah tanpa membangun ulang).
Ya, Swift mengizinkan direktif #if di dalam fungsi, loop, dan bahkan di dalam ekspresi. Ini adalah salah satu fitur yang tidak ada di versi awal Swift. Contoh: let x = #if DEBUG 1 #else 0 #endif — kode yang benar.
Pengembang Kotlin secara sadar menolak preprosesor, menganggapnya sebagai sumber kode rapuh. Sebagai gantinya, mereka menawarkan expect/actual (keamanan waktu kompilasi) dan Gradle sourceSets (isolasi tingkat file). Kedua pendekatan lebih andal daripada penggantian tekstual.
Bangun aplikasi dengan kombinasi bendera yang berbeda di CI. Untuk Swift: konfigurasikan skema Xcode terpisah dengan Active Compilation Conditions yang berbeda. Untuk Android: konfigurasikan Build Variants terpisah dan jalankan tes untuk masing-masing. Otomatisasi wajib.
Di Swift, kondisi #if adalah direktif kompiler. Jika kondisi itu sendiri salah secara sintaksis (misalnya, kesalahan ketik pada nama os()), kompiler akan mengeluarkan kesalahan kompilasi. Di C/C++, preprosesor tidak akan menemukan makro dan kondisi akan menjadi salah.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga