ما هي مكتبة Swinject ولماذا تحتاجها؟
Swinject هي مكتبة خفيفة الوزن لحقن التبعيات (Dependency Injection - DI) بلغة Swift. توفر طريقة بسيطة وتصريحية لإدارة التبعيات بين الكائنات، مما يجعل الكود أكثر نمطية، وقابلية للاختبار، وسهولة في الصيانة.
في النهج التقليدي، تقوم الكائنات بإنشاء تبعياتها بنفسها، مما يؤدي إلى ارتباط شديد في الكود. على سبيل المثال، قد يقوم الكلاس UserService بإنشاء نسخة من NetworkClient مباشرة. إذا أردت استبدال NetworkClient بكائن وهمي (mock) للاختبار، فسيتعين عليك تعديل كود UserService. تحل Swinject هذه المشكلة عن طريق نقل عملية إنشاء الكائنات وربطها إلى حاوية منفصلة.
المزايا الرئيسية لـ Swinject:
- تقليل الاقتران: الكلاسات لا تعرف كيف يتم إنشاء تبعياتها.
- تحسين قابلية الاختبار: سهولة استبدال التبعيات الحقيقية بكائنات وهمية (mocks) ونماذج اختبارية (stubs).
- تبسيط التكوين: يتم تجميع جميع تكوينات التبعيات في مكان واحد.
- دعم دورة الحياة: إدارة عمر الكائنات (مفرد - singleton، نسخة جديدة، إلخ).
- هندسة نظيفة: تشجع على اتباع مبادئ SOLID، ولا سيما مبدأ عكس التبعية (Dependency Inversion Principle).
تكون Swinject مفيدة بشكل خاص في المشاريع الكبيرة، حيث يزداد عدد الكلاسات وتبعياتها، ويصبح إدارتها يدويًا أمرًا معقدًا وعرضة للأخطاء.
تثبيت Swinject
يتم تثبيت Swinject عبر Swift Package Manager (SPM) أو CocoaPods. سنستعرض كلا الطريقتين.
التثبيت عبر Swift Package Manager (موصى به)
في Xcode، انتقل إلى File → Add Packages.... في شريط البحث، أدخل رابط المستودع: https://github.com/Swinject/Swinject.git. اختر الإصدار المطلوب (عادةً أحدث إصدار مستقر) واضغط Add Package.
بدلاً من ذلك، يمكنك إضافة التبعية إلى ملف Package.swift الخاص بك:
// swift-tools-version:5.7
import PackageDescription
let package = Package(
name: "YourProject",
dependencies: [
.package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: ["Swinject"])
]
)
التثبيت عبر CocoaPods
أضف إلى ملف Podfile الخاص بك:
pod 'Swinject', '~> 2.8'
ثم نفذ الأمر pod install.
ملاحظة: على عكس Python (pip) أو Node.js (npm)، لا يوجد مدير حزم موحد في Swift، لكن SPM هو المعيار والمدمج في Xcode.
بداية سريعة مع مثال برمجي
لننظر إلى مثال بسيط: لدينا بروتوكول LoggerProtocol وتنفيذه ConsoleLogger. نريد أن يتلقى الكلاس UserService المسجل (logger) عن طريق حقن التبعيات.
import Swinject
// 1. تعريف البروتوكول
protocol LoggerProtocol {
func log(message: String)
}
// 2. تنفيذ البروتوكول
class ConsoleLogger: LoggerProtocol {
func log(message: String) {
print("LOG: \(message)")
}
}
// 3. الكلاس الذي يستخدم التبعية
class UserService {
private let logger: LoggerProtocol
// الحقن عبر المُهيئ (Initializer)
init(logger: LoggerProtocol) {
self.logger = logger
}
func createUser(name: String) {
logger.log(message: "جارٍ إنشاء المستخدم: \(name)")
// منطق إنشاء المستخدم
}
}
// 4. إعداد الحاوية
let container = Container()
container.register(LoggerProtocol.self) { _ in ConsoleLogger() }
container.register(UserService.self) { r in
UserService(logger: r.resolve(LoggerProtocol.self)!)
}
// 5. الاستخدام
let userService = container.resolve(UserService.self)!
u