مقدمة: لماذا يعتبر حقن التبعية ضرورة
\nفي التطوير الحديث لتطبيقات iOS وmacOS، أصبحت نقاء الكود وقابليته للاختبار في المقدمة. أحد المبادئ الأساسية التي تمكن من تحقيق هذه الأهداف هو حقن التبعية (DI). بدون DI، غالباً ما تصبح الفئات "مترابطة بشكل صارم" (tightly coupled)، مما يعيق إعادة استخدامها وتعديلها، والأهم من ذلك، كتابة اختبارات الوحدة.
\nعملياً، يعني حقن التبعية في Swift أن الكائن يحصل على تبعياته من الخارج، بدلاً من إنشائها بنفسه. قد يبدو هذا تغييراً بسيطاً، لكنه يغير بنية التطبيق بشكل جذري. في هذه المقالة، سنستعرض أنماط DI الرئيسية في Swift: حقن المُنشئ، حقن الخاصية، واستخدام حاوية DI مع مثال Swinject. سيتم توضيح كل طريقة بأمثلة عملية لتطبيقات iOS/macOS.
\nهل أنت مستعد لجعل كودك أكثر مرونة وقابلية للاختبار؟ فلنبدأ.
\n\n1. حقن المُنشئ: الطريقة الأكثر موثوقية
\nحقن المُنشئ (Constructor Injection) هو إدخال التبعيات عبر المُهيئ (init) للفئة. تعتبر هذه الطريقة الأكثر تفضيلاً، لأنها تضمن أن الكائن يكون دائماً في حالة صالحة: جميع تبعياته إلزامية وغير قابلة للتغيير.
\nلنأخذ مثالاً. لنفترض أن لدينا فئة NetworkService مسؤولة عن طلبات HTTP، وفئة UserViewModel تستخدم هذه الخدمة.
// بروتوكول للتجريد\nprotocol NetworkServiceProtocol {\n func fetchUsers() async throws -> [User]\n}\n\n// التنفيذ الفعلي\nclass RealNetworkService: NetworkServiceProtocol {\n func fetchUsers() async throws -> [User] {\n // طلب HTTP فعلي\n return []\n }\n}\n\n// ViewModel مع حقن المُنشئ\nclass UserViewModel {\n private let networkService: NetworkServiceProtocol\n\n // يتم تمرير التبعية عبر المُهيئ\n init(networkService: NetworkServiceProtocol) {\n self.networkService = networkService\n }\n\n func loadUsers() async {\n do {\n let users = try await networkService.fetchUsers()\n print("تم تحميل \(users.count) مستخدم")\n } catch {\n print("خطأ: \(error)")\n }\n }\n}\n\n// الاستخدام\nlet service = RealNetworkService()\nlet viewModel = UserViewModel(networkService: service)\n\nالمزايا:
\n- \n
- التبعية مُعلنة بوضوح وإلزامية. \n
- لا يمكن إنشاء الكائن بدون التبعيات. \n
- مثالي لاختبارات الوحدة: سهولة استبدال التنفيذ الفعلي بـ Mock. \n
العيوب:
\n- \n
- إذا كانت التبعيات كثيرة، يصبح المُهيئ ضخماً. \n
- غير مناسب للتبعيات الاختيارية. \n
هذه الطريقة هي "المعيار الذهبي" لحقن التبعية. استخدمها حيثما أمكن.
\n\n2. حقن الخاصية: مرونة للتبعيات الاختيارية
\nحقن الخاصية (Property Injection) هو إدخال التبعيات عبر خصائص (property) الكائن. يُستخدم عادةً عندما لا تكون التبعية ضرورية لعمل الكائن أو عندما لا يمكن تمريرها عبر المُهيئ (مثل Storyboard/UIKit).
\nمثال كلاسيكي هو ViewController في iOS الذي يتم إنشاؤه من Storyboard. لا يمكنك تغيير init الخاص به، لذا تستخدم خاصية.
protocol LoggerProtocol {\n func log(message: String)\n}\n\nclass ConsoleLogger: LoggerProtocol {\n func log(message: String) {\n print("سجل: \(message)")\n }\n}\n\nclass MyViewController: UIViewController {\n // تبعية اختيارية\n var logger: LoggerProtocol?\n\n override func viewDidLoad() {\n super.viewDidLoad()\n logger?.log(message: "تم تحميل العرض")\n // إذا لم يتم تعيين logger، لن يحدث شيء\n }\n}\n\n// الإعداد (مثلاً