Dependency Injection: ما هو، حقن التبعيات في iOS و Android

المؤلف: IT Sectr نُشر: 2026-02-18 وقت القراءة: 9 دق

حقن التبعيات (DI) هي تقنية يحصل فيها الكائن على تبعياته من الخارج بدلاً من إنشائها بنفسه. DI هي تطبيق لمبدأ IoC (عكس التحكم) وتشكل الأساس لـ Dagger و Hilt و Swinject. حقن التبعيات يقلل من اقتران الكود، ويبسط الاختبار، ويجعل البنية مرنة. في Android، DI هو المعيار عبر Dagger Hilt من Google، وفي iOS عبر Swinject أو الحقن اليدوي. تعرف على المزيد في دليل DI لنظام Android.

الخلاصة

  • حقن التبعيات — يتم تمرير التبعيات إلى الكائن من الخارج، ولا يتم إنشاؤها داخلياً
  • عكس التحكم — DI يطبق مبدأ IoC، ويتم نقل تدفق التحكم إلى الحاوية
  • Dagger Hilt — معيار DI لنظام Android، المبني على Dagger من Google
  • Swinject — إطار DI شائع لنظام iOS و Swift
  • تقليل الاقتران — تعتمد الفئة على التجريدات، وليس على التطبيقات الملموسة

ما هو Dependency Injection: الجوهر وأنواع DI

حقن التبعيات هي تقنية يحصل فيها الكائن على التبعيات (الخدمات، المستودعات، الإعدادات) من خلال المُنشئ أو المُحدد أو الواجهة، بدلاً من إنشائها بنفسه باستخدام new. الهدف من DI هو تقليل الاقتران بين الفئات. إذا قامت فئة بإنشاء التبعيات بنفسها، فإنها ترتبط بشدة بتطبيقات محددة، مما يعقد الاختبار والتعديل. مع DI، تعمل الفئة مع تجريد (بروتوكول/واجهة)، ويتم توفير التطبيق الملموس من الخارج.

ثلاث طرق للحقن — حقن المُنشئ (عبر init/constructor)، حقن المُحدد (عبر الخاصية/setter)، حقن الواجهة (عبر طريقة واجهة). حقن المُنشئ هو الطريقة المفضلة: التبعيات مرئية بوضوح في التوقيع، ويتم إنشاء الكائن دائمًا في حالة صالحة. يستخدم حقن المُحدد للتبعيات الاختيارية بقيمة افتراضية. حقن الواجهة نادر، ويستخدم بشكل أساسي لحاويات DI.

نوع DIالطريقةمتى تستخدممثال
المُنشئمعاملات المُهيئالتبعيات الإلزاميةinit(service: ServiceProtocol)
الخاصيةخاصية الفئةالتبعيات الاختياريةvar service: ServiceProtocol?
الطريقةمعامل الطريقةالتبعيات المؤقتةfunc doWork(with service: Service)

حاوية DI — مكتبة تدير إنشاء التبعيات ودورة حياتها. تحتوي الحاوية على تسجيلات الأنواع (كل نوع تجريدي مُقابل بتطبيق ملموس) ومصنع لإنشاء الكائنات مع التبعيات المُحلَّلة. في Android — Dagger/Hilt، في iOS — Swinject، Needle، Dip. يمكن للحاوية إدارة النطاق: singleton (مثيل واحد لكل تطبيق)، نطاق الميزة (لكل شاشة) أو كائن جديد عند كل طلب.

Dagger Hilt: DI لنظام Android مع توليد الكود

Dagger Hilt هو غلاف حول Dagger من Google، مكتبة DI القياسية لنظام Android. يعمل Hilt على تبسيط Dagger: يزيل الإنشاء اليدوي للمكونات، ويضيف @HiltAndroidApp و @AndroidEntryPoint و @Module. يتكامل Hilt مع دورة حياة Android: ViewModel و Activity و Fragment و Service و BroadcastReceiver يمكنهم تلقي التبعيات من خلال التعليقات التوضيحية. يحدث توليد الكود في وقت التجميع — يقوم Dagger بتوليد تطبيقات المكونات، مما يعطي عبء runtime صفري.

kotlin
// فئة التطبيق
@HiltAndroidApp
class MyApp : Application()

// الوحدة — تحدد كيفية إنشاء التبعيات
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()

    @Provides
    @Singleton
    fun provideApiService(client: OkHttpClient): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .client(client)
            .build()
            .create(ApiService::class.java)
    }
}

// ViewModel يتلقى التبعية عبر المُنشئ
@HiltViewModel
class MainViewModel @Inject constructor(
    private val apiService: ApiService
) : ViewModel() {
    private val _state = MutableStateFlow(MainState.Loading)
    val state: StateFlow<MainState> = _state.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            _state.value = MainState.Success(apiService.getData())
        }
    }
}

// Activity — @AndroidEntryPoint يفعل DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
}

مكونات Dagger ونطاقات الرؤية — @Singleton (للتطبيق بالكامل)، @ActivityScoped (لـ Activity)، @FragmentScoped (لـ Fragment)، @ViewModelScoped (لـ ViewModel). يحدد اختيار النطاق عمر الكائن. @Singleton — مثيل واحد لكل عملية، مناسب لـ OkHttpClient وقواعد البيانات. @ActivityScoped — الكائن يعيش طالما يعيش Activity، للتبعيات على مستوى الشاشة. @ViewModelScoped — جديد في Hilt 2.45+، الكائن يعيش طالما يعيش ViewModel، مناسب لنطاقات coroutine.

Swinject: DI لنظام iOS بلغة Swift

Swinject هو إطار DI مفتوح المصدر شائع لنظام iOS. يوفر Swinject Container و Assemblies ونطاقات مختلفة. على عكس Dagger، يعمل Swinject في وقت التشغيل — يتم حل التبعيات ديناميكيًا دون توليد كود. هذا يجعل Swinject أسهل في الإعداد، لكن التصحيح أكثر صعوبة: خطأ التبعية غير المحلولة يظهر فقط في وقت التشغيل. يدعم Swinject حقن المُنشئ وحقن الخاصية وحقن الطريقة.

swift
import Swinject

// Assembly — مجموعة تسجيلات
class NetworkAssembly: Assembly {
    func assemble(container: Container) {
        container.register(NetworkServiceProtocol.self) { _ in
            NetworkService()
        }.inObjectScope(.container) // singleton

        container.register(UserRepositoryProtocol.self) { r in
            UserRepository(
                networkService: r.resolve(NetworkServiceProtocol.self)!
            )
        }
    }
}

// ViewModel عبر حقن المُنشئ
class ProfileViewModel: ObservableObject {
    private let repository: UserRepositoryProtocol

    init(repository: UserRepositoryProtocol) {
        self.repository = repository
    }

    @Published var user: User?
    func loadUser() {
        repository.fetchUser { [weak self] user in
            self?.user = user
        }
    }
}

// إعداد DI في AppDelegate أو التطبيق
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!

// حقن الخاصية لـ UIKit ViewController
container.register(UserViewController.self) { r in
    let vc = UserViewController()
    vc.viewModel = r.resolve(ProfileViewModel.self)
    return vc
}

نطاقات Swinject — .transient (كائن جديد في كل مرة)، .container (singleton لكل حاوية)، .graph (افتراضي — تتم مشاركة الكائن داخل رسم بياني واحد للتبعيات). لتطبيقات iOS، .container و .transient كافية. يدعم Swinject أيضًا Assembler — تجميع Assembly لبنية معيارية. للاختبار، يتم استبدال Assembly بـ MockAssembly، مما يتيح استبدال التبعيات دون تغيير كود الإنتاج.

مقارنة DI مع Service Locator والحقن اليدوي

DI مقابل Service Locator — كلا النمطين يحلان مشكلة إدارة التبعيات، ولكن بشكل مختلف. DI يحقن التبعيات في الكائن؛ Service Locator يوفر سجلاً عالمياً يطلب منه الكائن التبعيات بنفسه. DI يعلن التبعيات بوضوح من خلال المُنشئ (أو المُحدد). Service Locator يخفي التبعيات — يتم طلبها داخل الطريقة، مما يجعل التوقيع أقل إفادة. DI أسهل في الاختبار: يكفي تمرير mock إلى المُنشئ. Service Locator يتطلب إعداد السجل العالمي لكل اختبار.

الخاصيةحقن التبعياتService Locatorالحقن اليدوي
وضوح التبعياتفي المُنشئمخفية في جسم الطريقةواضحة
الاختبارMock في المُنشئإعداد LocatorMock في المُنشئ
تعقيد الإعداديتطلب حاوية DIسجل عالميإنشاء يدوي
عبء runtimeDagger — وقت التجميعبحث في runtimeلا يوجد

DI مقابل الحقن اليدوي — بدون حاوية DI، يتم إنشاء التبعيات يدويًا في المصانع أو AppDelegate. لـ 5-10 فئات، الحقن اليدوي أبسط — لا يتطلب تعلم Dagger أو Swinject. لـ 50+ فئة، يصبح الحقن اليدوي مشكلة: مُنشئات بها 5-6 معاملات، ترتيب إنشاء معقد، تكرار الكود. حاوية DI تؤتمت هذه العمليات وتوفر إدارة واضحة لدورة الحياة. الحقن اليدوي بدون حاوية هو خيار جيد للمشاريع الصغيرة والنماذج الأولية.

أفضل ممارسات حقن التبعيات

حقن المُنشئ — المعيار. استخدم دائمًا حقن المُنشئ للتبعيات الإلزامية. هذا يجعل التبعيات واضحة والكائن جاهزًا دائمًا للعمل. حقن المُحدد — فقط للتبعيات الاختيارية (مثل delegate أو listener). حقن الواجهة — لا تستخدمه إلا إذا كنت تكتب مكتبة DI خاصة بك. حقن المُنشئ هو الطريقة الوحيدة لضمان إنشاء الكائن في حالة صالحة.

فئة واحدة — مسؤولية واحدة. إذا كان مُنشئ الفئة يتطلب 5+ معاملات، فمن المحتمل أن الفئة تنتهك مبدأ المسؤولية الفردية. قسم الفئة إلى عدة فئات بعدد أقل من التبعيات. علامة: إذا كنت تكتب فئة ServiceManager بها 6 خدمات مختلفة — فهذا هو النمط المضاد God Object. استخرج منطق الأعمال إلى Use Cases (Interactors)، كل منها مع 1-2 تبعيات.

kotlin
// ❌ سيء: 6 تبعيات — God Object
class ProfileViewModel @Inject constructor(
    private val api: ApiService,
    private val db: Database,
    private val analytics: Analytics,
    private val prefs: Preferences,
    private val location: LocationProvider,
    private val notification: NotificationManager
)

// ✅ جيد: Use Cases مع 1-2 تبعيات
class ProfileViewModel @Inject constructor(
    private val loadProfileUseCase: LoadProfileUseCase,
    private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)

النطاق ودورة الحياة — اختر النطاق الصحيح لكل تبعية. Singletons: OkHttpClient، قاعدة البيانات، SharedPreferences. نطاق الميزة: المستودعات، Use Cases (إذا كانت بلا حالة). Transient: Value Objects، DateFormatter، المُحلِّلات. أخطاء النطاق مشكلة شائعة: singleton يخزن حالة الشاشة يؤدي إلى تسربات. في Android، @ActivityScoped من Hilt يحل هذه المشكلة؛ في Swinject، استخدم .container بحذر.

الأسئلة الشائعة

لماذا أحتاج DI إذا كان بإمكاني استخدام new فقط؟

new ينشئ اقتراناً قوياً بين الفئات — لا يمكنك تغيير التطبيق دون تعديل الكود. يصبح الاختبار صعباً: لا يمكنك حقن mock بدلاً من خدمة حقيقية. يتم انتهاك SRP: الفئة مسؤولة عن كل من منطق الأعمال وإنشاء التبعيات. DI يحل هذه المشكلات عن طريق حقن التبعيات من الخارج والعمل مع التجريدات.

Dagger Hilt أم Koin — ماذا تختار لنظام Android؟

Dagger Hilt هو المعيار من Google، DI في وقت التجميع مع توليد الكود، أداء أفضل وتكامل مع Jetpack. Koin هو DI في وقت التشغيل، أسهل في الإعداد، لكنه أبطأ ومع أخطاء في runtime. اختر Hilt للمشاريع الإنتاجية. Koin مناسب للنماذج الأولية والتطبيقات الصغيرة.

هل Swinject هو الخيار الوحيد DI لنظام iOS؟

لا. لنظام iOS تتوفر: Swinject (runtime، شائع)، Needle (وقت التجميع من Uber)، Dip (خفيف)، Weaver (قائم على Sourcery). Apple لا توفر حاوية DI مدمجة، لكن الحقن اليدوي عبر init هو ممارسة قياسية. لـ SwiftUI، غالباً ما يكون DI اليدوي عبر Environment أو @StateObject دون مكتبات خارجية كافياً.

هل يمكن استخدام DI بدون إطار عمل؟

نعم. الحقن اليدوي عبر المُنشئ هو DI بدون إطار عمل. Service Locator هو بديل بدون إطار عمل. المصانع و Factory Method هي أيضاً أشكال من DI. إطار العمل (Dagger، Swinject) يؤتمت التسجيل الروتيني وحل التبعيات، لكن لـ 10-20 فئة، DI اليدوي كافٍ.

هل DI هو نمط أم مبدأ؟

DI هو تقنية (قالب) تطبق مبدأ عكس التحكم. على عكس أنماط GoF، DI ليس له بنية صارمة من 3-4 فئات. DI هو طريقة لتنظيم التبعيات، وليس نمط تصميم. حاويات DI (Dagger، Swinject) هي أطر عمل تؤتمت هذه التقنية.

الملخص

  • DI — تقنية حقن التبعيات من الخارج عبر المُنشئ أو المُحدد أو الطريقة
  • Dagger Hilt — معيار DI لنظام Android مع توليد كود في وقت التجميع و @HiltViewModel
  • Swinject — DI في وقت التشغيل لنظام iOS مع Container و Assembly والنطاقات
  • حقن المُنشئ — الطريقة المفضلة للتبعيات الإلزامية
  • النطاق — Singleton للخدمات عديمة الحالة، نطاق الميزة للتبعيات على مستوى الشاشة
  • الاختبار — DI يبسط استبدال التبعيات بـ mock دون تغيير الكود

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

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

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

اقرأ أيضًا