Dependency Injection هو نمط شائع إلى حد ما يسمح لك بتكوين النظام بمرونة وبناء تبعيات مكونات هذا النظام بشكل صحيح على بعضها البعض. بفضل الكتابة ، يتيح لك Swift استخدام أطر ملائمة يمكنك من خلالها وصف رسم التبعية بإيجاز شديد. اليوم أريد أن أتحدث قليلاً عن أحد هذه الأطر - DITranquillity
.
سيغطي هذا البرنامج التعليمي ميزات المكتبة التالية:
- اكتب التسجيل
- التهيئة النشر
- التضمين في متغير
- التبعيات الدورية للمكون
- استخدام المكتبة مع
UIStoryboard
وصف المكون
سيتألف التطبيق من المكونات الرئيسية التالية: ViewController
، Router
، Presenter
، Networking
- هذه مكونات شائعة جدًا في أي تطبيق iOS.
سيتم تقديم ViewController
وجهاز Router
إلى بعضها البعض بشكل دوري.
تدريب
أولاً ، قم بإنشاء تطبيق عرض فردي في Xcode ، أضف DITranquillity باستخدام CocoaPods . قم بإنشاء التسلسل الهرمي الضروري للملفات ، ثم أضف وحدة تحكم ثانية إلى Main.storyboard وقم بتوصيلها باستخدام StoryboardSegue
. نتيجة لذلك ، يجب الحصول على بنية الملف التالية:
قم بإنشاء تبعيات في الفصول كما يلي:
إعلان المكون protocol Presenter: class { func getCounter(completion: @escaping (Int) -> Void) } class MyPresenter: Presenter { private let networking: Networking init(networking: Networking) { self.networking = networking } func getCounter(completion: @escaping (Int) -> Void) {
protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) {
protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() {
class ViewController: UIViewController { var presenter: Presenter! var router: Router! }
قيود
على عكس الفئات الأخرى ، لا يتم إنشاء ViewController
من قبلنا ، ولكن عن طريق مكتبة UIKit داخل تطبيق UIStoryboard.instantiateViewController
، وبالتالي ، باستخدام لوحة العمل ، لا يمكننا حقن التبعيات في ورثة UIViewController
باستخدام UIViewController
. هذا هو الحال مع ورثة UIView
و UITableViewCell
.
لاحظ أن الكائنات المخفية وراء البروتوكولات مضمنة في جميع الفئات. هذه واحدة من المهام الرئيسية لتنفيذ التبعيات - لجعل التبعيات ليس على التطبيقات ، ولكن على الواجهات. سيساعد ذلك في المستقبل على توفير تطبيقات بروتوكول مختلفة لإعادة استخدام المكونات أو اختبارها.
حقن التبعية
بعد إنشاء جميع مكونات النظام ، ننتقل إلى اتصال الكائنات فيما بينها. في DITranquillity ، نقطة البداية هي DIContainer
، التي تضيف التسجيل باستخدام طريقة container.register(...)
. لفصل التبعيات إلى أجزاء ، DIPart
DIFramework
و DIPart
، والتي يجب تنفيذها. للراحة ، DIFramework
فئة واحدة فقط من ApplicationDependency
، والتي ستنفذ DIFramework
وستكون بمثابة مكان تسجيل لجميع التبعيات. تتطلب واجهة DIFramework
تطبيق طريقة واحدة فقط - load(container:)
.
class ApplicationDependency: DIFramework { static func load(container: DIContainer) {
لنبدأ بأبسط تسجيل ليس له أي تبعيات - MyNetworking
container.register(MyNetworking.init)
يستخدم هذا التسجيل التنفيذ من خلال أداة التهيئة. على الرغم من حقيقة أن المكون نفسه لا يوجد لديه تبعيات ، يجب توفير المُهيئ لتوضيح كيفية إنشاء المكون للمكتبة.
وبالمثل ، سجل MyPresenter
و MyRouter
.
container.register1(MyPresenter.init) container.register1(MyRouter.init)
ملاحظة: لاحظ أنه لا يتم استخدام التسجيل ، ولكن register1
. لسوء الحظ ، من الضروري الإشارة إلى ما إذا كان الكائن يحتوي على تبعية واحدة فقط في المُهيئ. وهذا هو ، إذا كان هناك 0 أو اثنين أو أكثر من التبعيات ، تحتاج فقط إلى استخدام register
. هذا التقييد عبارة عن خطأ في الإصدار 4.0 من Swift.
حان الوقت لتسجيل ViewController
لدينا. إنها تحقن الكائنات ليس من خلال أداة التهيئة ، ولكن مباشرة في المتغير ، لذلك سيظهر وصف التسجيل أكثر من ذلك بقليل.
container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter)
بناء جملة النموذج \.presenter
هو SwiftKeyPath ، وبفضل ذلك يمكنك تنفيذ التبعيات بإيجاز. نظرًا لأن Router
و ViewController
دوريًا على بعضهما البعض ، فيجب الإشارة بوضوح إلى ذلك باستخدام cycle: true
. يمكن للمكتبة نفسها حل هذه التبعيات دون إشارة صريحة ، ولكن تم تقديم هذا الشرط حتى يفهم الشخص الذي يقرأ الرسم البياني على الفور أن هناك دورات في سلسلة التبعية. لاحظ أيضًا أن NOT ViewController.init
، ولكن ViewController.self
. تم توضيح ذلك أعلاه في قسم القيود .
من الضروري أيضًا تسجيل UIStoryboard
باستخدام طريقة خاصة.
container.registerStoryboard(name: "Main")
الآن وصفنا الرسم البياني التبعية بأكمله لشاشة واحدة. ولكن ليس هناك إمكانية الوصول إلى هذا الرسم البياني حتى الآن. يجب عليك إنشاء DIContainer
يسمح لك بالوصول إلى الكائنات الموجودة فيه.
static let container: DIContainer = { let container = DIContainer()
- تهيئة الحاوية
- أضف وصفًا للرسم البياني.
- نتحقق من أننا فعلنا كل ما هو صواب. إذا تم ارتكاب خطأ ، فلن يتعطل التطبيق أثناء حل التبعيات ، ولكن فورًا عند إنشاء رسم بياني
ثم تحتاج إلى جعل الحاوية نقطة انطلاق التطبيق. للقيام بذلك ، AppDelegate
بتطبيق طريقة didFinishLaunchingWithOptions
في didFinishLaunchingWithOptions
بدلاً من تحديد Main.storyboard
كنقطة انطلاق في إعدادات المشروع.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let storyboard: UIStoryboard = ApplicationDependency.container.resolve() window?.rootViewController = storyboard.instantiateInitialViewController() window?.makeKeyAndVisible() return true }
إطلاق
في البداية ، سيحدث انخفاض وسيفشل التحقق من الصحة للأسباب التالية:
- لن تجد الحاوية أنواع
Router
أو Presenter
أو Networking
لأننا سجلنا الكائنات فقط. إذا كنا نريد منح الوصول ليس للتطبيقات ، ولكن للواجهات ، يجب علينا تحديد الواجهات بشكل صريح - لا تفهم الحاوية كيفية حل التبعية الدورية ، لأنه من الضروري الإشارة صراحةً إلى الأشياء التي لا ينبغي إعادة إنشائها في كل مرة يحل فيها الرسم البياني.
من السهل إصلاح الخطأ الأول - هناك طريقة خاصة تسمح لك بتحديد البروتوكولات التي تتوفر بها الطريقة في الحاوية.
container.register(MyNetworking.init) .as(check: Networking.self) {$0}
لوصف التسجيل على النحو التالي ، نقول: كائن MyNetworking
يمكن الوصول إليه عبر بروتوكول Networking
. يجب أن يتم ذلك لجميع الكائنات المخفية تحت البروتوكولات. {$0}
نضيفه للتحقق الصحيح من الأنواع بواسطة المترجم.
الخطأ الثاني هو أكثر تعقيدا قليلا. من الضروري استخدام scope
المسمى ، والذي يصف عدد المرات التي يتم فيها إنشاء الكائن ومدى الحياة. لكل تسجيل مشارك في تبعية دائرية ، يجب عليك تحديد scope
يساوي objectGraph
. سيوضح ذلك للحاوية أنه أثناء الحل ، من الضروري إعادة استخدام نفس الكائنات التي تم إنشاؤها ، بدلاً من إنشاء كل مرة. وبالتالي ، اتضح:
container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) .lifetime(.objectGraph) container.register1(MyRouter.init) .as(check: Router.self) {$0} .lifetime(.objectGraph)
بعد إعادة التشغيل ، تنجح الحاوية في التحقق من الصحة بنجاح ، ويفتح ViewController لدينا مع التبعيات التي تم إنشاؤها. يمكنك وضع نقطة توقف في viewDidLoad
وتأكد.
الانتقال بين الشاشات
بعد ذلك ، قم بإنشاء فئتين صغيرتين ، SecondViewController
و SecondPresenter
، وأضف SecondViewController
إلى لوحة العمل وقم بإنشاء Segue
بينهما باستخدام المعرف "RouteToSecond"
، والذي يسمح لك بفتح وحدة التحكم الثانية من الأولى.
أضف تسجيلين آخرين إلى ApplicationDependency
لكل فئة من الفئات الجديدة:
container.register(SecondViewController.self) .injection(\.secondPresenter) container.register(SecondPresenter.init)
.as
هناك حاجة لتحديد .as
، لأننا لم نخفي SecondPresenter
وراء البروتوكول ، ولكن استخدم التطبيق مباشرة. بعد ذلك ، في طريقة viewDidAppear
التحكم الأولى ، نسميها performSegue(withIdentifier: "RouteToSecond", sender: self)
، ابدأ ، يتم فتح وحدة التحكم الثانية ، والتي يجب أن تكون ملحقة بها secondPresenter
. كما ترون ، شهدت الحاوية إنشاء وحدة تحكم ثانية من UIStoryboard
ونجح في إخماد التبعيات.
استنتاج
تتيح لك هذه المكتبة العمل بشكل مريح مع التبعيات الدورية ، ولوحات القصة ، واستخدام الاستدلال التلقائي للنوع بالكامل في Swift ، والذي يوفر بناء جملة قصيرًا ومرنًا لوصف رسم بياني التبعية.
مراجع
رمز عينة كاملة في مكتبة جيثب
الهدوء على جيثب
المادة الانجليزية