Условно компилирање у мобилним апликацијама — суштина, директиве и принцип рада

Аутор: 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.

Шта је условно компилирање

Условно компилирање — механизам у којем компилатор анализира директиве условног компилирања и укључује у излазни бинарни фајл само one блокове кода за које су услови испуњени. Ово омогућава постојање јединствене базе кода која се прилагођава различитим циљним платформама и конфигурацијама.

Концепт потиче из C/C++ са препроцесорским директивама #ifdef, #ifndef, #endif. У савременим језицима (Swift, Rust, Go) механизам ради на нивоу компилатора без посебног препроцесора, што повећава сигурност: условни блокови морају бити синтаксички исправни, чак и ако се не компилирају.

Према подацима Apple WWDC сесије „Embrace Swift” (2025), око 40% Swift пројеката користи условно компилирање за подршку iOS и macOS у једном target-у. За пројекте са 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. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође