کامپایل شرطی در برنامه‌های موبایل — مفهوم، دستورالعمل‌ها و اصل کار

نویسنده: 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 کد مشترک — کامپایل شرطی اساس توسعه فریم‌ورک‌های چندپلتفرمی اپل است.

کامپایل شرطی چیست

کامپایل شرطی — مکانیزمی است که در آن کامپایلر دستورالعمل‌های کامپایل شرطی را تحلیل کرده و تنها آن بلوک‌های کدی را که شرایط برای آنها برآورده می‌شود، در فایل باینری خروجی قرار می‌دهد. این امکان داشتن یک پایگاه کد واحد را فراهم می‌کند که با پلتفرم‌ها و پیکربندی‌های مختلف هدف سازگار می‌شود.

این مفهوم از 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/BJava/KotlinBuildConfig.FLAVOR

الگوهای ضد

خطرناک‌ترین الگوی ضد — گسترش دستورالعمل‌ها در سراسر کد. اگر هر فایل دوم حاوی #if باشد، این نشانه‌ای است که معماری نیاز به بازسازی دارد. راه‌حل صحیح — جدا کردن کد پلتفرم در پشت پروتکل‌ها/رابط‌ها و استفاده از تزریق وابستگی.

  • #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 از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید