Mobil tətbiqlərdə şərti kompilyasiya — mahiyyəti, direktivlər və iş prinsipi

Müəllif: IT Sectr Dərc olunub: 2026-06-01 Oxuma vaxtı: 9 dəq

Şərti kompilyasiya kompilyatora qurma mərhələsində məlum olan şərtlərdən asılı olaraq mənbə kodun hissələrini daxil etməyə və ya buraxmağa imkan verir. The Swift Programming Language (2026)-ya görə, #if direktivi maşın kodunun yaradılmasından əvvəl AST təhlili mərhələsində emal olunur. Şərti kompilyasiya tərtibatçılara təkrarlanma olmadan bir neçə platforma və konfiqurasiya üçün vahid kod bazasını saxlamaq imkanı verir.

Başlıca

  • Şərti kompilyasiya — platforma, konfiqurasiya və ya dil versiyası şərtlərinə görə kodun seçmə kompilyasiyası texnikası.
  • Direktivlər #if, #elseif, #else, #endif — Swift, C, C++, Objective-C-də şərti kompilyasiyanın əsas konstruksiyaları.
  • Kotlin preprosessor direktivlərinə malik deyil — bunun əvəzinə BuildConfig, expect/actual və sourceSets istifadə olunur.
  • Üstünlük — uyğun olmayan platformalar üçün kod kompilyasiya olunmur, bu da binar faylın ölçüsünü azaldır və səhvləri aradan qaldırır.
  • iOS/macOS ümumi kodu — şərti kompilyasiya Apple-ın çarpaz-platforma freymvorklarının hazırlanmasının əsasıdır.

Şərti kompilyasiya nədir

Şərti kompilyasiya — kompilyatorun şərti kompilyasiya direktivlərini təhlil etdiyi və çıxış binar fayla yalnız şərtləri ödənən kod bloklarını daxil etdiyi mexanizmdir. Bu, müxtəlif hədəf platformalara və konfiqurasiyalara uyğunlaşan vahid kod bazasına sahib olmağa imkan verir.

Konsepsiya C/C++-dən #ifdef, #ifndef, #endif preprosessor direktivləri ilə gəlmişdir. Müasir dillərdə (Swift, Rust, Go) mexanizm ayrıca preprosessor olmadan kompilyator səviyyəsində işləyir ki, bu da təhlükəsizliyi artırır: şərti bloklar kompilyasiya olunmasalar belə, sintaktik cəhətdən düzgün olmalıdır.

Apple WWDC “Embrace Swift” (2025) sessiyasının məlumatlarına görə, Swift layihələrinin təxminən 40%-i bir targetdə iOS və macOS-u dəstəkləmək üçün şərti kompilyasiyadan istifadə edir. UIKit və SwiftUI ilə layihələrdə UI kodu tez-tez #if os(iOS)#if os(macOS) direktivləri ilə ayrılır ki, bu da biznes mantiqinin təkrar istifadəsinə imkan verir.

Əsas üstünlük — kompilyasiya mərhələsində təhlükəsizlik. Uyğun olmayan platforma üçün kod sadəcə icra olunmur, həm də kompilyasiya olunmur. Bu o deməkdir ki, iOS-a xas koddakı səhvlər macOS üçün qurma zamanı özünü göstərməyəcək və əksinə. İcra zamanı yoxlamaları belə bir zəmanət vermir.

Swift-də şərti kompilyasiya

Swift dörd əsas direktiv təqdim edir: #if, #elseif, #else, #endif. C preprosessorundan fərqli olaraq, Swift bütün budaqlarda kodun sintaktik düzgünlüyünü tələb edir — kompilyator bütün kodu təhlil edir, lakin maşın kodunu yalnız aktiv budaqlar üçün yaradır.

Platforma yoxlamaları os()

Swift daxili yoxlama funksiyalarını dəstəkləyir: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). Bu funksiyalar tətbiqin qurulduğu hədəf platformanı yoxlayır. &&|| ilə kombinasiya mürəkkəb şərtlər yaratmağa imkan verir.

swift
// iOS, macOS və tvOS üçün vahid kod
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
    }
}

Kompilyator versiyasının yoxlanması

Swift kompilyator versiyasının yoxlanmasını dəstəkləyir: #if swift(>=5.9). Bu, bir neçə Swift versiyasını dəstəkləyən kitabxana və freymvorklar üçün faydalıdır. Dilin yeni imkanları (məsələn, Swift 5.9-da makrolar) belə bir yoxlama ilə qoruna bilər.

swift
// Geri uyğunluq
#if swift(>=5.9)
    @MainActor
    struct ModernView: View {
        var body: some View {
            Text("Müzasir SwiftUI")
        }
    }
#else
    struct ModernView: View {
        var body: some View {
            Text("Köhnə SwiftUI")
        }
    }
#endif

Modulun mövcudluğunun yoxlanması canImport()

canImport(ModuleName) funksiyası göstərilən modulun cari qurma mühitində mövcud olub-olmadığını yoxlayır. Bu, ən çevik mexanizmdir: konkret platformaya bağlı deyil. Məsələn, CoreHaptics istifadə edən kod yalnız bu freymvorkun mövcud olduğu cihazlarda kompilyasiya olunacaq.

swift
#if canImport(CoreHaptics)
    import CoreHaptics

    class HapticManager {
        private var engine: CHHapticEngine?

        func playTapFeedback() {
            guard let engine else { return }
            // Taktil əks-sədanın implementasiyası
        }
    }
#endif

Kotlin və Android-də alternativlər

Kotlin bir dil olaraq preprosessor direktivlərinə malik deyil. Bunun əvəzinə Android ekosistemi üç alternativ təklif edir: BuildConfig sahələri (icra zamanı yoxlamaları), sourceSets (bütün faylların əvəz edilməsi) və expect/actual (Kotlin Multiplatform-da).

Gradle-də Source Sets

Gradle sourceSets müxtəlif fléyorlar və ya qurma növləri üçün siniflərin müxtəlif implementasiyalarına sahib olmağa imkan verir. src/debug/ kataloqunda debug üçün implementasiya, src/release/-də isə release üçün yerləşir. Qurma zamanı Gradle münasib sourceSet-i seçir və yalnız onun fayllarını kompilyasiya edir.

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) {
        // Release-də No-op
    }
}

Kotlin Multiplatform-da Expect/Actual

KMP expect (ümumi koddə bəyannamə) və actual (konkret platforma üçün implementasiya) mexanizmini təqdim edir. Bu, kompilyasiya mərhələsində işləyən mexanizmdir: iOS üçün iOS sourceSet-dən actual implementasiya, Android üçün isə Android sourceSet-dən kompilyasiya olunur. Hədəf olmayan implementasiyalar kompilyasiya olunmur.

kotlin
// commonMain — expect bəyannaməsi
expect fun getPlatformName(): String

// androidMain — Android üçün actual
actual fun getPlatformName(): String =
    "Android \${Build.VERSION.SDK_INT}"

// iosMain — iOS üçün actual
actual fun getPlatformName(): String =
    UIDevice.current.systemName() + " " + UIDevice.current.systemVersion

C/C++ preprosessoru və NDK

Android NDK vasitəsilə nativ kitabxanaların hazırlanmasında klassik C/C++ preprosessoru #ifdef, #ifndef, #define direktivləri ilə istifadə olunur. Swift-dən fərqli olaraq, C preprosessoru mətn səviyyəsində işləyir — aktiv olmayan budaqlardakı kod sintaktik cəhətdən səhv ola bilər.

NDK platforma bayraqları

NDK hər platforma üçün makrolar təyin edir: __ANDROID__ (Android), __APPLE__ (iOS/macOS), __linux__ (Linux). Arxitekturalar üçün: __arm__, __aarch64__, __x86_64__. Bu makrolar kompilyator tərəfindən hədəf platforma üçün qurma zamanı avtomatik olaraq təyin edilir.

cpp
// Android və iOS üçün nativ kod
#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 ilə işləyərkən yadda saxlamaq lazımdır ki, C/C++ preprosessoru mətn əvəzetməsidir. Aktiv olmayan budaqda sintaktik səhv varsa, kompilyator onu görməyəcək, lakin səhv #define səbəbindən aktiv budaq pozulursa — səhv üzə çıxacaq. #define zəncirlərini minimuma endirmək və constexpr sabitlərindən istifadə etmək tövsiyə olunur.

UniFFI və Mozilla Application Services vasitəsilə mobil inkişafda da istifadə olunan Rust üçün Cargo.toml-də öz mexanizmi — feature flags mövcuddur. Rust-da #[cfg(target_os = "android")] kimi bayraqlar Swift direktivlərinə bənzər şəkildə işləyir: yoxlama preprosessor deyil, kompilyator səviyyəsində aparılır. Bu, Rust-u bir kod bazasından Android və iOS üçün kompilyasiya olunmalı nativ kitabxanalar üçün cəlbedici seçim edir.

Praktik ssenarilər və anti-nümunələr

Şərti kompilyasiya ciddi şəkildə müəyyən edilmiş ssenarilərdə effektivdir. Səhv istifadə edildikdə, test etmək və saxlamaq çətin olan pis iy verən kod yaradır. Düzgün ssenariləri vɘ tipik səhvləri nəzərdən keçirək.

Düzgün ssenarilər

Birinci ssenari — platforma abstraksiyası: vahid fasad, içərisində şərti kompilyasiya platforma implementasiyasını seçir. İkinci — debug və profilinq: buraxılışa daxil olmamalı olan tərtibatçı alətləri. Üçüncü — geri uyğunluq: minimum versiya yenilənənə qədər köhnə əməliyyat sistemi versiyalarının dəstəyi.

SsenariDilŞərt
Platforma abstraksiyasıSwift#if os(iOS)
DebugSwift/ObjC#if DEBUG
Geri uyğunluqSwift#if swift(>=5.7)
Nativ kitabxanaC/C++#ifdef __ANDROID__
A/B testiJava/KotlinBuildConfig.FLAVOR

Anti-nümunələr

Ən təhlükəli anti-nümunə — direktivlərin bütün kod boyunca yayılması. Hər ikinci faylda #if varsa, bu arxitekturanın yenidən qurulmasını tələb etdiyinə işarədir. Düzgün həll yolu platforma kodunu protokollar/interfeyslər arxasına çəkmək və Dependency Injection istifadə etməkdir.

  • Hər faylda #if — arxitektura anti-nümunəsi. Platforma kodu protokollarla izolyasiya edilməlidir.
  • İç-içə #if — tez oxunmaz hala gəlir. İç-içəlik dərinliyi 2 səviyyədən çox olmamalıdır.
  • Bütün funksiyaların təkrarlanması — əgər funksiya tamamilə #if və #else-də köçürülübsə, onu ümumi hissəyə çıxarmaq lazımdır.
  • Test etmə — aktiv olmayan budaqlardakı kod test edilmir. Bütün mümkün kombinasiyaların CI-də qurulması zəruridir.
  • Sehrli bayraqlar — yeni tərtibatçı komandasının bilmədiyi sənədləşdirilməmiş bayraqlar.

Tez-tez verilən suallar

Şərti kompilyasiya icra zamanı yoxlamalarından nə ilə fərqlənir?

Şərti kompilyasiya kompilyasiya mərhələsində işləyir: aktiv olmayan kod binar fayla daxil olmur. İcra zamanı yoxlamaları (if / switch) həmişə kompilyasiya olunur, şərt icra zamanı yoxlanır. Birincisi təhlükəsiz və səmərəli, ikincisi daha çevikdir (yenidən qurmadan dəyişdirilə bilər).

Swift-də funksiya daxilində #if istifadə etmək olarmı?

Bəli, Swift funksiyalar daxilində, dövrlərdə və hətta ifadələr daxilində #if direktivlərinə icazə verir. Bu, Swift-in ilk versiyalarında olmayan imkanlardan biridir. Məsələn: let x = #if DEBUG 1 #else 0 #endif — düzgün kod.

Niyə Kotlin preprosessor əlavə etmədi?

Kotlin-in yaradıcıları preprosessordan şürurlə üz çevirdilər, çünki onu kövrək kodun mənbəyi hesab edirlər. Bunun əvəzinə onlar expect/actual (kompilyasiya səviyyəsində təhlükəsizlik) və Gradle sourceSets (fayl səviyyəsində izolyasiya) təklif edirlər. Hər iki yanaşma mətn əvəzetməsindən daha etibarlıdır.

Aktiv olmayan #if budaqlarındakı kodu necə test etməli?

CI-da müxtəlif bayraq kombinasiyaları ilə tətbiqi qurmaq. Swift üçün: müxtəlif Active Compilation Conditions ilə ayrı Xcode sxemləri konfiqurasiya etmək. Android üçün: ayrı Build Variants konfiqurasiya etmək və hər biri üçün testlər işlətmək. Avtomatlaşdırma məcburidir.

#if şərti sintaktik səhv ehtiva edərsə nə olar?

Swift-də #if şərti kompilyator direktividir. Şərtin özü sintaktik cəhətdən səhvdirsə (məsələn, os() adında səhv), kompilyator kompilyasiya xətası verəcək. C/C++-də preprosessor sadəcə makro tapmayacaq və şərt yalan olacaq.

Nəticə

  • Şərti kompilyasiya — qurma mərhələsində hədəf olmayan kodu istisna edən kompilyasiya texnikası, icra zamanı yoxlamalarından fərqli olaraq.
  • Swift os(), canImport(), swift() ilə #if-i dəstəkləyir — bütün budaqların sintaktik düzgünlüyünü tələb edən kompilyasiya vaxtı təhlükəsiz direktivlər.
  • Kotlin preprosessor əvəzinə expect/actual və Gradle sourceSets istifadə edir — daha etibarlı, lakin daha az çevik yanaşmalar.
  • NDK-də C/C++ __ANDROID__, __APPLE__ platforma makroları ilə klassik mətn preprosessoru #ifdef / #ifndef istifadə edir.
  • Düzgün tətbiq — platforma abstraksiyası, debug, geri uyğunluq. Səhv tətbiq — hər faylda #if, dərin iç-içəlik, sehrli bayraqlar.
  • CI məcburidir — bütün bayraq kombinasiyaları avtomatik qurulmalı və test edilməlidir, əks halda aktiv olmayan budaqlardakı kod ölü olur.

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun