التجميع الشرطي في تطبيقات الجوال — الجوهر والتوجيهات ومبدأ العمل

المؤلف: 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.

ما هو التجميع الشرطي

التجميع الشرطي هو آلية يقوم فيها المترجم بتحليل توجيهات التجميع الشرطي ويضمن في الملف الثنائي الناتج فقط كتل الكود التي تتحقق شروطها. هذا يسمح بوجود قاعدة كود واحدة تتكيف مع منصات وتكوينات مستهدفة مختلفة.

المفهوم جاء من C/C++ مع توجيهات المعالج المسبق #ifdef و #ifndef و #endif. في اللغات الحديثة (Swift, Rust, Go)، تعمل الآلية على مستوى المترجم بدون معالج مسبق منفصل، مما يزيد الأمان: يجب أن تكون الكتل الشرطية صحيحة نحويًا حتى لو لم يتم تجميعها.

وفقًا لجلسة Apple WWDC «Embrace Swift» (2025)، حوالي 40% من مشاريع Swift تستخدم التجميع الشرطي لدعم iOS و macOS في هدف واحد. للمشاريع التي تستخدم UIKit و SwiftUI، غالبًا ما يتم تقسيم كود الواجهة بتوجيهات #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("Modern SwiftUI")
        }
    }
#else
    struct ModernView: View {
        var body: some View {
            Text("Legacy 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

sourceSets في Gradle تسمح بوجود تطبيقات مختلفة للكلاسات لأنواع بناء أو نكهات مختلفة. في المجلد src/debug/ يوجد التطبيق للتصحيح، في src/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 في الإصدار
    }
}

Expect/Actual في Kotlin Multiplatform

KMP يوفر آلية expect (إعلان في الكود المشترك) و actual (تطبيق لمنصة محددة). هذه آلية في وقت التجميع: لنظام iOS يتم تجميع التطبيق الفعلي من sourceSet iOS، لنظام Android — من sourceSet Android. التطبيقات غير المستهدفة لا يتم تجميعها.

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، يوجد آليته الخاصة — أعلام الميزات في Cargo.toml. أعلام مثل #[cfg(target_os = "android")] في Rust تعمل بشكل مشابه لتوجيهات Swift: يتم الفحص على مستوى المترجم وليس المعالج المسبق. هذا يجعل Rust خيارًا جذابًا للمكتبات الأصلية التي يجب أن تُجمع لنظامي Android و iOS من قاعدة كود واحدة.

السيناريوهات العملية والأنماط المضادة

التجميع الشرطي فعال في سيناريوهات محددة بدقة. عند استخدامه بشكل غير صحيح، فإنه يخلق كودًا ذا رائحة كريهة (code smell) يصعب اختباره وصيانته. دعنا نستعرض السيناريوهات الصحيحة والأخطاء النموذجية.

السيناريوهات الصحيحة

السيناريو الأول — تجريد المنصة: واجهة واحدة يختار داخلها التجميع الشرطي تطبيق المنصة. الثاني — التصحيح والتنميط: أدوات المطور التي لا يجب أن تصل إلى الإصدار. الثالث — التوافق العكسي: دعم إصدارات نظام التشغيل القديمة حتى يتم تحديث الحد الأدنى للإصدار.

السيناريواللغةالشرط
تجريد المنصةSwift#if os(iOS)
التصحيحSwift/ObjC#if DEBUG
التوافق العكسيSwift#if swift(>=5.7)
مكتبة أصليةC/C++#ifdef __ANDROID__
اختبار A/BJava/KotlinBuildConfig.FLAVOR

الأنماط المضادة

أخطر نمط مضاد هو انتشار التوجيهات في جميع أنحاء الكود. إذا كان كل ملف ثانٍ يحتوي على #if، فهذه إشارة على أن البنية تحتاج إعادة هيكلة. الحل الصحيح هو إخراج كود المنصة خلف بروتوكولات/واجهات واستخدام حقن التبعيات.

  • #if في كل ملف — نمط مضاد معماري. يجب عزل كود المنصة خلف بروتوكولات.
  • #if المتداخلة — تصبح غير قابلة للقراءة بسرعة. عمق التداخل يجب ألا يتجاوز مستويين.
  • تكرار دوال كاملة — إذا تم نسخ دالة بالكامل في #if و #else، يجب إخراجها إلى جزء مشترك.
  • الاختبار — الكود داخل الفروع غير النشطة لا يُختبر. من الضروري وجود بنيات CI لجميع التركيبات الممكنة.
  • أعلام سحرية — أعلام غير موثقة لا يعرفها فريق التطوير الجديد.

الأسئلة المتكررة

ما الفرق بين التجميع الشرطي وفحوصات وقت التشغيل؟

التجميع الشرطي يعمل في وقت التجميع: الكود غير النشط لا يصل إلى الملف الثنائي. فحوصات وقت التشغيل (if / switch) تُجمع دائمًا، يتم التحقق من الشرط أثناء التنفيذ. الأول أكثر أمانًا وكفاءة، والثاني أكثر مرونة (يمكن تغييره دون إعادة بناء).

هل يمكن استخدام #if داخل دالة في Swift؟

نعم، Swift يسمح بالتوجيهات #if داخل الدوال والحلقات وحتى داخل التعبيرات. هذه إحدى الميزات التي كانت مفقودة في الإصدارات المبكرة من Swift. على سبيل المثال: let x = #if DEBUG 1 #else 0 #endif — كود صحيح.

لماذا لم يضف Kotlin معالجًا مسبقًا؟

مطورو Kotlin رفضوا عمدًا المعالج المسبق، معتبرين إياه مصدرًا للكود الهش. بدلاً من ذلك، يقدمون expect/actual (أمان في وقت التجميع) و sourceSets في Gradle (عزل على مستوى الملفات). كلا النهجين أكثر موثوقية من الاستبدال النصي.

كيف نختبر الكود داخل فروع #if غير النشطة؟

قم ببناء التطبيق مع مجموعات مختلفة من الأعلام في CI. لـ Swift: قم بإعداد مخططات Xcode منفصلة بشروط تجميع نشطة مختلفة. لـ Android: قم بإعداد Build Variants منفصلة وشغّل الاختبارات لكل منها. الأتمتة إلزامية.

ماذا يحدث إذا كان شرط #if يحتوي على خطأ نحوي؟

في Swift، شرط #if هو توجيه مترجم. إذا كان الشرط نفسه غير صحيح نحويًا (مثل خطأ إملائي في اسم os())، سيصدر المترجم خطأ في التجميع. في C/C++، المعالج المسبق ببساطة لن يجد الماكرو وسيصبح الشرط خاطئًا.

الخلاصة

  • التجميع الشرطي — تقنية تجميع تستبعد الكود غير المستهدف في وقت البناء، على عكس فحوصات وقت التشغيل.
  • Swift يدعم #if مع os() و canImport() و swift() — توجيهات آمنة في وقت التجميع تتطلب صحة نحوية لجميع الفروع.
  • Kotlin يستخدم expect/actual و sourceSets في Gradle بدلاً من المعالج المسبق — نهج أكثر موثوقية لكن أقل مرونة.
  • C/C++ في NDK يستخدم المعالج المسبق النصي الكلاسيكي #ifdef / #ifndef مع ماكرو المنصات __ANDROID__ و __APPLE__.
  • الاستخدام الصحيح — تجريد المنصة، التصحيح، التوافق العكسي. الاستخدام غير الصحيح — #if في كل ملف، تداخل عميق، أعلام سحرية.
  • CI إلزامي — يجب بناء واختبار جميع تركيبات الأعلام تلقائيًا، وإلا يصبح الكود في الفروع غير النشطة ميتًا.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا