حقن التبعية (Dependency Injection) في Swift: المبادئ، الأمثلة والتطبيق

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

ما هو حقن التبعية (Dependency Injection) ولماذا هو مطلوب في Swift

حقن التبعية (DI) هو نمط تصميم (design pattern) يحصل فيه الكائن على تبعياته من الخارج، بدلاً من إنشائها بنفسه. في سياق Swift لتطبيقات iOS وmacOS، يساعد DI في جعل الكود أكثر مرونة وقابلية للاختبار والصيانة. بدلاً من الربط الصارم بين الفئات عبر init() أو let، تقوم بتمرير التبعيات عبر المُنشئ (constructor) أو الخاصية (property) أو الدالة (method).

المزايا الرئيسية لـ DI:

  • قابلية الاختبار: يمكنك بسهولة استبدال الكائنات الحقيقية بـ mocks أو stubs في اختبارات الوحدة (unit tests).
  • الترابط الضعيف: تعتمد الفئات على التجريدات (البروتوكولات) وليس على التطبيقات الملموسة.
  • تبسيط إعادة الهيكلة: التغييرات في جزء من الكود لا تؤدي إلى كسر جزء آخر.
  • هندسة نظيفة: يتبع الكود مبدأ المسؤولية الواحدة (Single Responsibility) ومبدأ عكس التبعية (Dependency Inversion Principle من SOLID).

في Swift، يعتبر DI مهمًا بشكل خاص عند العمل مع UIKit وSwiftUI وCoreData والشبكة والخدمات الأخرى. بدونه، تخاطر بالحصول على "كائنات إلهية" (god objects) وصعوبات في إعادة استخدام الكود.

الطرق الأساسية لحقن التبعيات في Swift

1. الحقن عبر المُنشئ (Constructor Injection)

الطريقة الأكثر شيوعًا والموصى بها. يتم تمرير التبعيات عند إنشاء الكائن وتصبح غير قابلة للتغيير (let).

protocol NetworkServiceProtocol {
    func fetchData() -> String
}

class NetworkService: NetworkServiceProtocol {
    func fetchData() -> String {
        return "بيانات من الخادم"
    }
}

class ViewModel {
    private let networkService: NetworkServiceProtocol

    // الحقن عبر المُنشئ
    init(networkService: NetworkServiceProtocol) {
        self.networkService = networkService
    }

    func loadData() -> String {
        return networkService.fetchData()
    }
}

// الاستخدام
let service = NetworkService()
let viewModel = ViewModel(networkService: service)
print(viewModel.loadData()) // "بيانات من الخادم"

المزايا: التبعيات واضحة، الكائن دائمًا في حالة صالحة. العيب: مع وجود عدد كبير من التبعيات، يصبح المُنشئ ضخمًا.

2. الحقن عبر الخاصية (Property Injection)

يتم تعيين التبعية بعد إنشاء الكائن. يُستخدم غالبًا للخدمات الاختيارية أو عند العمل مع Storyboard/XIB.

class UserViewController: UIViewController {
    var analyticsService: AnalyticsServiceProtocol?

    override func viewDidLoad() {
        super.viewDidLoad()
        analyticsService?.trackEvent("الشاشة مفتوحة")
    }
}

// الحقن
let vc = UserViewController()
vc.analyticsService = AnalyticsService()

العيب: يجب أن تكون الخاصية اختيارية (optional) أو لها قيمة افتراضية، مما قد يؤدي إلى أخطاء في وقت التشغيل إذا نسيت تعيين التبعية.

3. الحقن عبر الدالة (Method Injection)

يتم تمرير التبعية كمعامل للدالة. مناسب للحالات التي تكون فيها التبعية مطلوبة فقط لعملية واحدة.

class DataManager {
    func processData(using service: NetworkServiceProtocol) -> String {
        let rawData = service.fetchData()
        return "تمت المعالجة: \\(rawData)"
    }
}

let manager = DataManager()
let result = manager.processData(using: NetworkService())
print(result) // "تمت المعالجة: بيانات من الخادم"

مثال على تطبيق DI في تطبيق iOS

لننظر إلى مثال واقعي: تطبيق لعرض قائمة المستخدمين. سنقوم بإنشاء البروتوكولات والفئات مع حقن التبعيات.

// 1. البروتوكولات
protocol UserRepositoryProtocol {
    func getUsers() -> [String]
}

protocol UserViewModelProtocol {
    var users: [String] { get }
    func loadUsers()
}

// 2. تطبيق المستودع
class UserRepository: UserRepositoryProtocol {                

المدونات

توصيات الكتب